How to Use The Forwarded Ref in React

How to Use The Forwarded Ref in React

Photo by Gaelle Marcel on Unsplash

Dealing with refs has evolved a lot since React’s beginnings. React has gone from using a ref property on the component class and passing a string to the ref property of an element, to using ref callbacks that receive the DOM reference as an argument, to ref objects that expose the DOM reference in the on the current property of the ref object. Use of the ref object has also evolved, created with createRef and now more commonly used with the useRef hook.

As a component author, exposing the ref of an underlying element has also evolved. Originally it had to be exposed using some arbitrary prop, though conventionally named innerRef , and mapping it to the ref of the underlying element. Though this worked, React recently added a better way to expose the ref of an underlying element. Using React.forwardRef we can pass a function component and now have the ref forwarded to the component as the second argument after the props argument. Then it is as simple as mapping that ref to the underlying element, like this:

const Div = React.forwardRef((props, ref) => {
  return <div ref={ref} {...props} />;
});

And now we can use our component as if it was any other React element:

const App = () => {
  const divRef = React.useRef();

  return <Div ref={divRef}>{/*...*/}</Div>;
};

It would seem that all of our problems have been solved, but there is one final problem that has yet to be solved: using the forwarded ref. Sometimes we don’t want to just expose the ref, but use that same ref inside our component. One could naively, assume that you could simply use that same ref, like this:

const Div = React.forwardRef((props, ref) => {
  useTheNode(ref.current);
  return <div ref={ref} {...props} />;
});

Two assumptions make this technique impractical. The first problem should be obvious in that the ref, though exposed, might not be used and therefore might be null.

The second problem might not be as obvious. In our implementation, we are assuming a ref object is being passed in, ref objects are not the only thing that can be passed as to the ref property. One can still pass aref callback, which means that we cannot simply use the current property on the ref because we don’t know if it is a ref object or a ref callback.

Ultimately, what we need is some sort of hook that we can pass in the forwarded ref to and return a consistent ref object that we can use predictably, like this:

const Div = React.forwardRef((props, ref) => {
  const safeRef = useForwardedRef(ref);
  useTheNode(safeRef.current);
  return <div ref={safeRef} {...props} />;
});

The first thing we need to do in our new hook is to generate a ref object that we will return and use in our component:

const useForwardedRef = (ref) => {
  const innerRef = useRef(null);

  return innerRef;
};

Now we need to synchronize the new refs by doing a side effect. Any time we need to do a side effect, we need to use the useEffect hook. We need to check for three conditions, is the ref null, is the ref a function, or is it an object. If it is null, we do nothing. If it is a function, we call the function by passing innerRef.current to the function. And finally, if it is a ref object we can just make assign innerRef.current to ref.current.

Here is what our hook looks like now:

const useForwardedRef = (ref) => {
  const innerRef = useRef(null);

  useEffect(() => {
    if (!ref) return;
    if (typeof ref === "function") {
      ref(innerRef.current);
    } else {
      ref.current = innerRef.current;
    }
  });

  return innerRef;
};

Given that we want to this to synchronize any time anything changes, we won’t pass in a dependency array to the useEffect hook.

There you have it, a nice simple hook that will allow you to have a safe and predictable ref object while still exposing it via forwardRef. Recently, I have started a new Open Source Library that I am calling Bedrock Layout Primitives. One of the modules, installable via npm, written for this library is exactly the above code. Feel free to check out the docs for useForwardedRef and check out the code at Github.

Did you find this article valuable?

Support Travis Waith-Mair by becoming a sponsor. Any amount is appreciated!