nanopop icon indicating copy to clipboard operation
nanopop copied to clipboard

Usage with React

Open mod3x opened this issue 5 years ago • 11 comments

Hey there. Is it safe to use with react lifecycle hooks? As I noticed nanopop has no method to destroy an instance and react will try to create another one every re-render. Thanks

mod3x avatar Aug 14 '20 13:08 mod3x

here is example snippet

React.useLayoutEffect(() => {
  if (!isOpened) {
    return
  }

  const _reference = reference.current
  const _popper = popper.current

  let nanopop: NanoPop

  if (_reference && _popper) {
    nanopop = new NanoPop(_reference, _popper, {
      margin: 4,
      position: 'bottom-end',
    })

    nanopop.update()
  }
  
  // expected behavior
  return () => nanopop.destroy()
}, [isOpened])

mod3x avatar Aug 14 '20 13:08 mod3x

Hi :) NanoPop does not need to be destroyed manually as it does not bind any events to an elemenet. The only reason for re-creating a class is that it's convenient to just call .update() without re-providing any options. If a nanopop instance falls out of scope it'll get garbage collected automatically, no need to do any cleanup manually.

simonwep avatar Aug 14 '20 15:08 simonwep

Maybe it was a bad design decision to use a class, I'll consider releasing a second version with just a function called usePopper or something like that to make it look less stateful (considering that using new is quite expensive).

simonwep avatar Aug 14 '20 15:08 simonwep

Maybe it was a bad design decision to use a class, I'll consider releasing a second version with just a function called usePopper or something like that to make it look less stateful (considering that using new is quite expensive).

I would be very grateful to you!

7iomka avatar Aug 14 '20 17:08 7iomka

it does not bind any events to an elemenet

That lib becomes even better for me lol. Thanks, I think you can close this issue. Cheers

mod3x avatar Aug 14 '20 17:08 mod3x

it does not bind any events to an elemenet

That lib becomes even better for me lol. Thanks, I think you can close this issue. Cheers

Can you show me your example of using this plugin? I want to create my own dropdown and use this plugin as a dropdown content positioning tool. I have a problem that the reference element is missing in the dom, so I cannot initialize the plugin. The workaround that I use is to wait for the time when the content is first mounted in the dom (I use portals), at which point I initialize nanoPop for the first time) Thanks

7iomka avatar Aug 14 '20 18:08 7iomka

Version 2 is in progress... I hope this will only take a few days and I'll ping you @7iomka as soon as it's "finish" since there are major API changes :)

@mod3x I'll keep this open as long as the second version is in progress, you made me realize that the current API doesn't make that much sense as it's a really low level library to position things. At first I tried to imitate Popper but this is definitely going in a completely separate direction! 😄

simonwep avatar Aug 15 '20 16:08 simonwep

Okay I've managed to create a minimal demo on codesandbox. Make sure to checkout the latest README :)

simonwep avatar Aug 19 '20 11:08 simonwep

Great work! I'll update to the v2 :)

mod3x avatar Aug 19 '20 16:08 mod3x

Okay I've managed to create a minimal demo on codesandbox. Make sure to checkout the latest README :)

Thank you! One question: I see your content of dropdown is always prerendered and persist on page. In real-world projects we have dynamically created content (like from api) or static content, but displayed with React.createPortal only if dropdown is opened (local state changed by click on trigger). It is possible to handle this case? Same question is for case when component that has ref of trigger - changed.

In prev. version of your lib i write this code

...
    const [pop, setPop] = React.useState(null);
    const prevTrigger = usePrevious(trigger);
    const refUpdate = React.useRef(false);
    useEffect(() => {
      const isEqKeys = trigger?.key === prevTrigger?.key;
      if (!isEqKeys) {
        refUpdate.current = true;
      }
      if (pop && !refUpdate.current) return;
      if (!refTrigger?.current || !refContent?.current) return;
      if (!isOpen) return;
      setPop(
        new NanoPop(refTrigger.current, refContent.current, {
          forceApplyOnFailure: true,
          position: position || 'bottom-middle',
          variantFlipOrder: {
            start: 'sme',
            middle: 'mse',
            end: 'ems',
          },
          positionFlipOrder: {
            top: 'tb',
            right: 'rltb',
            bottom: 'bt',
            left: 'lrbt',
          },
        }),
      );
      refUpdate.current = false;
    }, [pop, isOpen, trigger, prevTrigger, refContent?.current, refTrigger?.current]);

    useEffect(() => {
      // eslint-disable-next-line no-unused-expressions, @typescript-eslint/no-unused-expressions
      showPortal && pop && pop.update();
    }, [pop, showPortal, windowWidth, windowHeight]);
...

7iomka avatar Aug 20 '20 17:08 7iomka

My current hack-workaround (with css animation + delay) https://codesandbox.io/s/distracted-haibt-15li4?file=/src/App.js Interesting your comments on this

7iomka avatar Aug 20 '20 17:08 7iomka