DEV Community

Cover image for Real Life Example - UseImperativeHandle
Daniel Bellmas
Daniel Bellmas

Posted on • Updated on

Real Life Example - UseImperativeHandle

1. Wait wait...what is it?

useImperativeHandle allows us to pass values and functions from a Child component to a Parent using a ref.
From there, the Parent can either use it itself or pass it to another Child.

Note that you can only pass a ref as a prop to a child that wraps its component in forwardRef.


Code examples are much better than words when it comes to understanding so here is one:

// Parent Component
const App = () => {
  const ref = useRef();

  return (
    <div>
      <ComponentWithButton ref={ref} />
      <button onClick={() => ref.current.increment()}>another button</button>
    </div>
  );
};

// Child Component
const ComponentWithButton = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({increment}))

  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  return (
    <div>
      <button onClick={increment}>click</button>
      <h2>Count: {count}</h2>
    </div>
  )
})

Enter fullscreen mode Exit fullscreen mode

In the example above, we are changing the count variable in the parent component with the help of useImperativeHandle and forwardRef.

2. Why?

The general pattern in React is to have a unidirectional flow of data.
In cases where bidirectional dataflow is needed, we can use libraries such as Redux or React context.

However, in some cases, using those is simply just overkill.
This is where useImperativeHandle comes in.


Now that we have some understanding of the hook and when we want to use it let's move to the Real Life Example...

We have a Settings page that allows the user to update and edit his information and notification preferences.

The component has sections and every section is a form that is responsible for changing data related to the user (A section for his profile info, his privacy settings, and his notifications settings).

const Section = ({ name, text, fields, schema }) => {
  const { control, handleSubmit, reset, formState } = useForm({
    mode: 'onChange',
    defaultValues: fields.reduce((acc, field) => ({ ...acc, [field.name]: field.defaultValue }), {})
  });

  return (
    <section className={styles.section}>
      <Title text={text} />
      <form onSubmit={handleSubmit(onSubmit)}>
        {fields.map(field => (
          <Field key={field.name} {...field} control={control} />
        ))}
      </form>
    </section>
  );
};
Enter fullscreen mode Exit fullscreen mode

Every section is rendered in the Settings component - The Parent Component:

const Settings = () => (
   <main className={styles.main}>
     {SECTIONS.map(section => (
        <Section key={section.name} {...section}  />
      ))}
   </main>
);
Enter fullscreen mode Exit fullscreen mode

Supposedly, everything is fine a Parent component that renders children...but what happens when we want to trigger the submit function of every section by clicking a global button?
We will need some way to allow the parent to control, that's where useImperativeHandle comes in.

We will add the hook in the Section component and wrap it with the forward ref so that we can pass a ref from Settings:

const Section = React.forwardRef(({ name, text, fields, schema },ref) => {
  const { control, handleSubmit, reset, formState } = useForm({
    mode: 'onChange',
    defaultValues: fields.reduce((acc, field) => ({ ...acc, [field.name]: field.defaultValue }), {})
  });


  useImperativeHandle(ref, () => ({
    submit() {
     handleSubmit(onSubmit)();
    }
  }));


  return (
    <section className={styles.section}>
      <Title text={text} />
      <form onSubmit={handleSubmit(onSubmit)}>
        {fields.map(field => (
          <Field key={field.name} {...field} control={control} />
        ))}
      </form>
    </section>
  );
});
Enter fullscreen mode Exit fullscreen mode

With the help of the hook we are able to create some sort of an API for the parent to use, in this example we are exposing the submit() function that we we'll be able to call.

Now our Settings component will look like so:

const Settings = () => {
 const refProfile = useRef();
 const refNotifications = useRef();
 const refPrivacy = useRef();

// The SECTIONS object is a configuration object that will
// hold the refs among the rest of the data

 const onSubmitAll = () => {
    SECTIONS.forEach(({ ref }) => {
      ref.current.submit();
    });
  };

  return (
     <main className={styles.main}>
       {SECTIONS.map(section => (
       // The ref of each section i passed here in the spread 
       // operation.
          <Section key={section.name} {...section}  />        
        ))}
     </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

That's it! We did it!
We Passed the control back to the parent without importing or using a more complex library.

We Did It


3. Conclusion

I don't mean to disappoint but React doesnโ€™t recommend using this hook. (there will most likely be another way in which you can do this without using the hook.)

Full disclosure, I ended up changing the component structure
But! nonetheless, it was super fun learning about this mysterious hook that is rarely used.

I hope you had fun too ๐Ÿ™๐Ÿผ Thank you for reading!!

Well that was fun

Latest comments (6)

Collapse
 
yarivshamash profile image
Yariv Shamash

Great read!
Even though it is advised against and I will probably not use it in my career. Reading of your journey with the task was nice and very relatable :)

Collapse
 
danielbellmas profile image
Daniel Bellmas

Thank you Yariv!!

Collapse
 
1chris_alex profile image
Christian Alexsander

Can you show a typescript's ref correctly typed for useImperativeHandle?

Collapse
 
danielbellmas profile image
Daniel Bellmas

The ref's type will be:
React.Ref<{ submit(): void }>
for more information check out this site:react-typescript-cheatsheet.netlif...

Collapse
 
danacoding profile image
DanaCoding

I don't quite agree with your comparison of Ref and Redux. Because my understanding is that Ref is more to operate on the HTML DOM.

Collapse
 
danielbellmas profile image
Daniel Bellmas

Yes usually useRef is used for DOM nodes but in this case (with the help of forwardRefand useImerativeHandle) we are able to create some sort of an API the parent can use, much like the global functions and state Redux gives us.

If you want to read more I found this link: reactjs.org/docs/refs-and-the-dom....