useImperativeHandle

Understanding and Using the useImperativeHandleHook in React

Have you ever needed to customise the way your child component interacts with its parent? Imagine you’re building a reusable component like a modal or an input field, and you want the parent component to have control over specific actions—like focusing on an input or opening the modal.

In React, child components expose their refs to the parent using the ref attribute. However, sometimes the default behaviour of refs doesn’t suit your needs. This is where useImperativeHandle comes to the rescue. It allows you to define what a parent component can "see" and "control" when interacting with a child component.

By the end of this lecture, you'll understand:

  1. What useImperativeHandle does.
  2. How to use it effectively in real-world scenarios.
  3. When (and when not) to use it.

1. Understanding Refs and the Default Behavior

Refs in React are a way to directly access a DOM element or a child component instance. By default:

  • The ref attribute gives access to the DOM node (e.g., <input>) or the component instance.
  • However, there’s no way to customise what gets exposed to the parent.

For example:

function ChildComponent() {
  return <input type="text" />;
}

function ParentComponent() {
  const inputRef = React.useRef();
  
  React.useEffect(() => {
    inputRef.current.focus(); // Directly focuses the input
  }, []);
  
  return <ChildComponent ref={inputRef} />;
}

This works fine for accessing basic DOM elements. But what if you want to expose custom methods or only specific properties to the parent? Let’s look at how useImperativeHandle fits in.


2. What is useImperativeHandle?

useImperativeHandle allows you to control the value exposed through a ref from a child component. It enables you to:

  1. Hide certain implementation details.
  2. Expose custom methods or state to the parent.

Syntax

useImperativeHandle(ref, createHandle, [dependencies])
  • ref: The forwarded ref from the parent component.
  • createHandle: A function that returns the object to expose.
  • [dependencies]: Optional. React will recreate the handle when these values change.

3. 👾Basic Example

Let’s create a simple component where the parent can focus on an input via a custom method.

Step 1: Forward the Ref

To use useImperativeHandle, you must forward the ref to the child component using React.forwardRef.

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

  // Step 2: Use useImperativeHandle to expose methods
  React.useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
  }));

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

Step 3: Access the Custom Method from the Parent

Now, the parent can call the focus method.

function ParentComponent() {
  const inputRef = React.useRef();

  return (
    <div>
      <InputWithFocus ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>
        Focus Input
      </button>
    </div>
  );
}

Here, the parent only has access to the focus method, not the entire DOM node.


4. Real-World Use Case: A Modal Component

Imagine a reusable modal where the parent can control when to open, close, or reset its content.

const Modal = React.forwardRef((props, ref) => {
  const [isOpen, setIsOpen] = React.useState(false);

  React.useImperativeHandle(ref, () => ({
    open: () => setIsOpen(true),
    close: () => setIsOpen(false),
    toggle: () => setIsOpen((prev) => !prev),
  }));

  return isOpen ? (
    <div style={{ border: "1px solid black", padding: "1rem" }}>
      <h2>Modal</h2>
      <button onClick={() => setIsOpen(false)}>Close</button>
    </div>
  ) : null;
});

In the parent:

function ParentComponent() {
  const modalRef = React.useRef();

  return (
    <div>
      <button onClick={() => modalRef.current.open()}>Open Modal</button>
      <Modal ref={modalRef} />
    </div>
  );
}

5. When to Use and When Not to Use

Use When:

  • You need to customize the exposed behavior of a child component.
  • You’re creating reusable components with specific methods like modals, tooltips, or inputs.

Avoid When:

  • The parent doesn’t need to control the child directly.
  • You can achieve the behavior through props or callbacks.

6. Advantages and Disadvantages

👍🏽 Advantages:

  1. Encapsulation: Control what the parent can access.
  2. Flexibility: Expose only relevant details or methods.

👎🏽 Disadvantages:

  1. Complexity: Can make components harder to understand.
  2. Overhead: Adds extra code; use it only when necessary.

Conclusion

Key Takeaways:

  1. useImperativeHandle customizes what a child component exposes via a ref.
  2. It’s especially useful for reusable components like modals and inputs.
  3. Use it sparingly—prefer simpler patterns when possible.

Common Pitfalls:

  • Forgetting to use React.forwardRef.
  • Exposing too many details, breaking encapsulation.

By mastering useImperativeHandle, you can build more flexible and maintainable React components while maintaining control over their behavior.


Assessment

1. Coding Exercise

Create a Dropdown component where the parent can:

  • Open the dropdown.
  • Close the dropdown.
  • Check if the dropdown is open.

Starter code:

const Dropdown = React.forwardRef((props, ref) => {
  const [isOpen, setIsOpen] = React.useState(false);

  React.useImperativeHandle(ref, () => ({
    // Implement methods here
  }));

  return isOpen ? (
    <div style={{ border: "1px solid black" }}>
      <p>Dropdown Content</p>
      <button onClick={() => setIsOpen(false)}>Close</button>
    </div>
  ) : null;
});

function ParentComponent() {
  const dropdownRef = React.useRef();

  return (
    <div>
      <button onClick={() => dropdownRef.current.open()}>Open Dropdown</button>
      <Dropdown ref={dropdownRef} />
    </div>
  );
}

2. Quiz

Slides

So far, we have been focusing purely on the application performance and user experience. It's time to cater to developers and development experience. useImperative handle creates comfortable APIs for your developers. Let's go!


When working with child components, we often need to control what the parent can access through a ref. The useImperativeHandle hook allows us to define a custom interface for this interaction.


This hook is particularly useful in scenarios where we build components like modals, dropdowns, or tooltips that require specific actions from the parent.


To use useImperativeHandle, the child component must forward the ref using React.forwardRef.


The useImperativeHandle hook accepts three arguments: the ref being forwarded, a function that creates the handle, and an optional array of dependencies.


With this hook, you define specific methods or properties the parent can access, hiding implementation details.


In this example, the parent passes the ref to the child component, which expects to populate the ref's current value with the defined imperativeHandle, containing the focus function.


InputWithFocus component uses the useImperativeHandle to expose the API containing the focus method to its parent using the provided ref. It uses its internal inputRef to target the input element in the implementation.


The parent component then uses the custom focus method to programmatically focus the input.


A common use case for useImperativeHandle is creating a modal component where the parent can control its visibility. Here, we define a reusable modal component. The parent can call open, close, or toggle to control its state. Can you edit this example and implement a parent component calling the provided handles?


Let's talk about when to use useImperativeHandle and when best to avoid it!


Use useImperativeHandle when you want to customise what the parent can interact with, especially for reusable UI components like modals or dropdowns.


For example, focusing an input or toggling a modal requires direct control, making this hook valuable.


If the parent can achieve the same functionality by passing props or using callbacks, useImperativeHandle may not be necessary.


Adding unnecessary complexity with this hook can make components harder to understand and maintain.


Let's summarise! Using this hook, you define exactly what a parent component can access in its child.


This hook shines when you want to expose specific methods in components like modals or inputs.


While powerful, overusing useImperativeHandle can lead to overly complex and difficult-to-maintain components.