forwardRef and useImperativeHandle

forwardRef and useImperativeHandle

·

3 min read

I wrote an article about ref in React before. In that article I wrote some typical usage of the useRef hook. In this article, let see another 2 apis about ref in React: forwardRef and useImperativeHandle.

So one typical usage of ref is to use it to store DOM node. Look at below example.

function App() {
  const [value, setValue] = useState("");
  const inputRef = useRef();

  return (
    <>
      <input
        ref={inputRef}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <br />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

This works well.

Now this input element has some special logic we want to make it a react component. So we create this component and pass ref as a prop to it. Example as below.

const MyInput = (props) => {
  return <input ref={props.ref} />;
};

function App() {
  const [value, setValue] = useState("");
  const inputRef = useRef();

  return (
    <>
      <MyInput
        ref={inputRef}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <br />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

Does this work? Actually no. If we run this code, we should see an error in console.

Screenshot 2022-11-25 at 20.08.52.png

Basically, this means ref is a special attribute in React and we cannot pass it down like a normal prop.

So how could we pass this ref down and hook it into the real input element? That's what this forwardRef function do.

What we should do is just wrap the component with forwardRef and pass the ref as a second parameter.

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

That's how we pass ref down from parent component to child component in React.

Then what is the purpose of useImperativeHandle? Well this hook is used to customize the value of the passed down ref.

In above example, we hook the passed down ref into the real input element, so the parent component could use this ref to access the element inside child component. Actually, this is not mandatory. With useImperativeHandle, instead of setting ref as a DOM node, we can customize the value of ref.

Take a look at below example.

const MyInput = React.forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(
    ref,
    () => {
      return {
        focus: () => inputRef.current.focus(),
        scrollIntoView: () => inputRef.current.scrollIntoView(),
      };
    },
    []
  );

  return <input ref={inputRef} />;
});

This example achieve the same effect with the first example. But thers ia a big difference. In first example, we expose the real input element to parent component, so parent component could call any method of the element. In this example, we create a new internal inputRef to access the input element, and only provide 2 methods focus and scrollIntoView to the parent component.

This hook can be more useful when the parent component needs to access multiple element inside the child component. We can expose no element at all, and wrap all the method the parent component needs into one ref attribute.

So in a word, forwardRef allows us pass ref down and useImperativeHandle allows us customize the value of ref. That's all.