React: Question: How to use "useRef" when it is passed through Portal?

Created on 13 Feb 2020  路  9Comments  路  Source: facebook/react

Hi. I create ref in root component in my app. When i bind ref to element, which is a child of another element that is render in the portal, ref "current" property is always undefined.

Sandbox with example here:
https://codesandbox.io/s/refs-through-portals-test-o5lqr

How can i use refs with portals with expected behaviour?

Invalid Question

All 9 comments

This has nothing to do with portals. Modal is not mounted originally, so the ref is not populated.
Also using ref.current as a dependency in useEffect makes no sense, since the component is not rerendered when ref changes.

Also using ref.current as a dependency in useEffect makes no sense, since the component is not rerendered when ref changes.

@vkurchatkin Using of ref.current as dependency of effect is necessary, because toggleModal trigger rerender of component =) at least I expect it...

This has nothing to do with portals. Modal is not mounted originally, so the ref is not populated.

Ref should be updated just after rerender by toggleModal but when modal is shown, ref.current is still undefined.

but when modal is shown, ref.current is still undefined.

That is not the case, the ref is fine, you just always log before it is populated

That is not the case, the ref is fine, you just always log before it is populated

@vkurchatkin Yes, setTimout in effect is helped to understand that DOM element was set to ref after calling effect:

  useEffect(() => {
    setTimeout(() => {
      console.log("modalRef.current: ", modalRef.current); // modalRef.current is DOM element
    }, 1000);
  }, [modalRef.current]);

But how can i properly handle ref with DOM element just in effect without additional props like "onMount" for Layer or another useState in this case?

In other words, how normal is this option?

const App = () => {
  const [isModalOpen, toggleModal] = useState(false);
  const [modalEl, setModalEl] = useState(null);

  useEffect(() => {
    console.log("modalEl: ", modalEl);
  }, [modalEl]);

  return (
    <div className="app">
      <h2>Refs through portals test</h2>

      <button onClick={() => toggleModal(true)}>Show modal</button>

      {isModalOpen && (
        <Modal onClose={() => toggleModal(false)}>
          <h3>Test modal</h3>
          <div ref={setModalEl}>Test div with ref prop</div>
        </Modal>
      )}
    </div>
  );
};

I mean, are there any more obvious ways to use ref in such cases?

I mean, are there any more obvious ways to use ref in such cases?

It really depends on what you need to do with the ref

@vkurchatkin I need to use DOM element in third party lib (body-scroll-lock) for prevent scroll on body

Ok, so here are some thoughts. When you want to apply some DOM-base library that's what you usually do (and that's probably what you want to do):

const ref = useRef();

React.useEffect(() => {
  libraryFunction(ref.current);
}, []);

return <div ref={ref}></div>

However, it is only safe to do if you pass the ref to a node that is available right after the component is mounted and until the component is unmounted.

Some examples where it won't work:

return <div>{ foo ? <div ref={ref}/>: null }</div>

div can be mounted later and then remounted multiply times

return <Foobar><div ref={ref}/></Foobar>

Who knows, what Foobar does to it children? Could just pass them through, could render conditionally.

So, if you want to use this pattern, you need to move ref in such a way that is not rendered conditionally and is not wrapped in any custom components that doesn't guarantee to pass through their children unconditionally.

function ModalContent() {
 const ref = useRef();
 // ...
  return <>
       <h3>Test modal</h3>
        <div ref={ref}>Test div with ref prop</div>
    </>
}

@vkurchatkin Thanks a lot, issue resolved

Was this page helpful?
0 / 5 - 0 ratings