0

Does anyone knows hows the right approach to pass callback references using react hooks. I'm trying to convert a modal that is built in a class component, to a hook component, but I'm not sure what's the correct way to do it.

  onOpen = () => {
   this.setState({ isOpen: true }, () => {
    // Ref for the button
    this.closeButtonNode.focus();
   });
   this.toggleScrollLock();
  };

And this is how I pass the the reference in the code

     <ModalContent
      buttonRef={(n) => {
        this.closeButtonNode = n;
      }}
      {// More props...}
    />

And the modal content component has the buttonRef like this

         <button
          type="button"
          className="close"
          aria-labelledby="close-modal"
          onClick={onClose}
          ref={buttonRef}
        >
          <span aria-hidden="true">×</span>
        </button>

So when the modal pops I was able to get focus in my button close, with hooks, the only way I managed to replicate the behavior is to add an useEffect hook that listen to the isOpen state like this:

useEffect(() => {
 if (isOpen) closeButtonNode.current.focus();
}, [isOpen]);

const onOpen = () => {
 setIsOpen(true);
 toggleScrollLock();
};

And this is how I pass the prop

  const closeButtonNode = useRef(null);
  return (
  <ModalContent
    buttonRef={closeButtonNode}
    {// More props...}
  />
  )

And I just use it like a regular ref, without passing a callback function, this works but I wonder why it works that way and why I cannot set the focus on the onOpen function like the class based component.

This is the sandbox if you want to check the full code. https://codesandbox.io/s/hooks-modal-vs-class-modal-bdjf0

jean182
  • 3,213
  • 2
  • 16
  • 27

1 Answers1

0

Why I cannot set the focus on the onOpen function like the class based component

Because when onOpen function get called open toggle still false and it will get updated after the modal is already opened. Please note that useState doesn't have a second argument (callback) like setState in class based component as you have done to set the focus. That why you needed to use useEffect.

You can test that by setting a delay using setTimeout after you set open to true like so:

const onOpen = () => {
  setIsOpen(true);
  setTimeout(() => closeButtonNode.current.focus())
};

Although, your approach using useEffect would be maybe better option.

codeSandbox example.

awran5
  • 4,333
  • 2
  • 15
  • 32
  • thanks that indeed help but I was wondering if I'm doing the right approach or there is a better alternative to do it? – jean182 Mar 30 '20 at 16:33
  • Either way is fine as long your app is not that "heavy" but personally, I'd use `useEffect` as its already meant to handle your app [side effect](https://reactjs.org/docs/hooks-reference.html#useeffect) plus setTimeout is not always that [reliable](https://stackoverflow.com/questions/21097421/what-is-the-reason-javascript-settimeout-is-so-inaccurate) – awran5 Mar 30 '20 at 17:24