useImperativeHandle
Hook in ReactHave 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:
useImperativeHandle
does.Refs in React are a way to directly access a DOM element or a child component instance. By default:
ref
attribute gives access to the DOM node (e.g., <input>
) or the component instance.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.
useImperativeHandle
?useImperativeHandle
allows you to control the value exposed through a ref from a child component. It enables you to:
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.Let’s create a simple component where the parent can focus on an input via a custom method.
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" />;
});
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.
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>
);
}
useImperativeHandle
customizes what a child component exposes via a ref
.React.forwardRef
.By mastering useImperativeHandle
, you can build more flexible and maintainable React components while maintaining control over their behavior.
Create a Dropdown
component where the parent can:
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>
);
}
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.