DEV Community

Cover image for useImperativeHandle: the most unknown React hook
Romain Trotard
Romain Trotard

Posted on • Edited on • Originally published at romaintrotard.com

useImperativeHandle: the most unknown React hook

Among all the native React hooks that exist, there are well known ones like useState, useEffect and less known ones like useImperativeHandle.

This last hook is the subject of this article that I teased in my last article about React references.

At the end of this post, you will know what problem is solved with it, how to use it and a real use case where it's needed to be more concrete.

Buckle up, let's go.

Introduction

In React, like in some other libraries, the data flow is unidirectional going top-down in the component tree. It means that a parent component can configure a child component thanks to properties.
So in a sense, a child can have access to an element of the parent when passed as property. But a parent can't have access to an element of the child, or so you think.

Unidirectional data flow

Note: We can inverse the data flow, by passing callback and value, for example when controlling components :)

If you want to expose a callback from the Child it can expose a register to register this callback:

function Child({ register }) {
  useEffect(() => {
    const aFunction = () =>
      console.log("A function inside the FC");
    register(aFunction);
  }, [register]);

  return <p>Child</p>;
}

function Parent() {
  const childCallback = useRef();

  const register = useCallback((callback) => {
    // I use a ref but could be a state 
    // if needed to display JSX
    childCallback.current = callback;
  }, []);

  return <Child register={register} />;
}
Enter fullscreen mode Exit fullscreen mode

Well, it works but it adds some boilerplate which is not the easiest to understand how it works.
It's time to go deep in the subject.

Warning: You should not have to expose a state or variable from a child, in this case you would have to change the API and put the state up.

Note: If you want to know more about React state, you can read my article Things you need to know about React state.


Some context with Class components

Firstly, I would like to talk about the behavior of ref with Class component.

Note: If you want to know more about ref you can see my article Things you need to know about React ref.

When we pass a ref to a Class component then we get the React element instance.

class ClassComponent extends React.Component {
  aFunction = () => console.log("A function inside the CC");

  render() {
    return <p>A class component</p>;
  }
}

function Parent() {
  const myRef = useRef();

  useEffect(() => {
    // Will log the React element instance
    console.log(myRef.current);
  }, []);

  return <ClassComponent ref={myRef} />;
}
Enter fullscreen mode Exit fullscreen mode

It will then log:

React element instance

Thanks to that, you can call imperatively any method of a Class component child from its parent thanks to the ref.

function Parent() {
  const myRef = useRef();

  return (
    <>
      <ClassComponent ref={myRef} />
      <button
        type="button"
        onClick={() => myRef.current.aFunction()}
      >
        Executes aFunction
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note: Even if you have access to the render method, do not call it!


Work with Functional components

If you try to do the same with a Functional child component, you will get the log undefined.

function FunctionalComponent() {
  const aFunction = () =>
    console.log("A function inside the FC");

  return <p>A functional component</p>;
}

const ForwardedRefFunctionalComponent = React.forwardRef(
  FunctionalComponent
);

function Parent() {
  const myRef = useRef();

  useEffect(() => {
    // It will log `undefined`
    console.log(myRef.current);
  }, []);

  return <ForwardedRefFunctionalComponent ref={myRef} />;
}
Enter fullscreen mode Exit fullscreen mode

Note: We need to forwardRef with Functional component when customizing the reference named ref. But you can pass a reference as an usual prop and customize it too without the need of forwardRef.

function FunctionalComponent({ aRef }) {
  const aFunction = () =>
    console.log("A function inside the FC");

  return <p>A functional component</p>;
}

function Parent() {
  const myRef = useRef();

  return <ForwardedRefFunctionalComponent aRef={myRef} />;
}
Enter fullscreen mode Exit fullscreen mode

You probably have guessed it, useImperativeHandle will help you to solve it. The hook allows to expose some method of a child FC to its parent by customizing the passed reference.


How to use it?

Now that we have the purpose of this hook, let's see how to use it.

It takes 3 parameters:

  • the reference to customize
  • the APIs to expose as a callback
  • an optional array of dependencies (when handler depends on state): has the same behavior than useEffect array dependency

Note: Like useEffect, if you do not pass an array (as third argument), the API callback will be executed at each render.

useImperativeHandle(ref, createHandle, [deps])
Enter fullscreen mode Exit fullscreen mode

For example with the the previous example it will be:

function FunctionalComponent(props, ref) {
  useImperativeHandle(ref, () => ({
    aFunction: () =>
      console.log("A function inside the FC"),
  }));

  return <p>A functional component</p>;
}
Enter fullscreen mode Exit fullscreen mode

And now the log will be:

useImperative console.log

Amazing, right? :)

Note: You can also attach the ref to a DOM node (not possible with Class component) ;)


A use case example

Well, now that you know how to use the hook, it's time to see a real use case where the component API is good and it's useful to use useImperativeHandle.

When displaying a list with a lot of elements, for example like Twitter, you can encounter some layout performances issues. This is where virtualization comes in handy.
Basically, only the elements displayed on the screen are present in the DOM (with few element before and after) which makes the DOM much lighter.

To do that you would make a component named VirtualizedList which will handle virtualization. Behind the hood, there are some calculations in function of the height of rows and handling position to know which rows need to be in the DOM and where they are displayed on the screen.

We would like to be able to scroll to a specific items, in an imperative way. It's the perfect use case for useImperativeHandle:

function VirtualizedList(props, ref) {
  useImperativeHandle(ref, () => ({
    scrollTo: (itemId) => {
      // Do some process
    },
  }));

  return (
    <ul>
      {
        // Right items to display
      }
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Alright, now you have seen you can expose an API from a child to its parent, but do not overused it. Most of the time you will think you need it, it's probably that the API of your component is not right.
It's possible to do it in Class components and in Functional components as well. In FC, do not forget to forwardRef otherwise it would not work.


Do not hesitate to comment and if you want to see more, you can follow me on Twitter or go to my Website.

Top comments (0)