useImperativeHandle
React is declarative by design: parents describe what a child should look like through props, not how it should behave step by step. Occasionally, though, a parent genuinely needs to tell a child to do something—focus a field, scroll to a row, start playing a video. useImperativeHandle lets a component define exactly which imperative methods it exposes through its ref, replacing the raw DOM node with a small, curated API you control.
Why an imperative escape hatch exists
Most coordination between components flows through props and state. But some interactions are inherently imperative—they happen now, in response to an event, and have no meaningful “rendered” representation. Focusing an input after a validation error, scrolling a list to the selected item, or seeking a media player are all actions, not states. Modeling them as props is awkward and fragile, so React offers useImperativeHandle as a deliberate, narrow escape hatch.
By default, attaching a ref to a child component would give the parent the child’s underlying DOM node (or null for a plain function component). useImperativeHandle intercepts that ref and substitutes an object of your choosing. The parent then calls only the methods you decided to publish, never the full DOM surface.
Pairing with forwardRef
A function component does not receive ref as a normal prop. To accept one, wrap the component in forwardRef, which passes the incoming ref as a second argument. Inside, useImperativeHandle(ref, createHandle, deps) populates that ref with the object returned by createHandle.
import { forwardRef, useImperativeHandle, useRef } from "react";
const TextField = forwardRef(function TextField(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = "";
},
}));
return <input ref={inputRef} {...props} />;
});
The parent receives { focus, clear }—nothing else. The actual <input> DOM node stays private. This is the whole point: you expose a tiny, intentional contract instead of leaking implementation details.
import { useRef } from "react";
function SignupForm() {
const emailRef = useRef(null);
function handleSubmit(event) {
event.preventDefault();
// Imperatively focus the field after a failed submit
emailRef.current.focus();
}
return (
<form onSubmit={handleSubmit}>
<TextField ref={emailRef} placeholder="Email" />
<button type="submit">Sign up</button>
</form>
);
}
In React 19 you can accept
refas a regular prop on a function component, makingforwardRefoptional. For libraries supporting React 18 and earlier,forwardRefremains the portable choice.
Keeping the exposed surface small
The value of an imperative handle comes from its restraint. Publish verbs the parent legitimately needs—focus, scrollToItem, play, reset—and nothing more. Resist the temptation to forward the DOM node directly, because doing so re-couples the parent to the child’s internals and defeats the encapsulation that makes the component reusable.
import { forwardRef, useImperativeHandle, useRef } from "react";
const VideoPlayer = forwardRef(function VideoPlayer({ src }, ref) {
const videoRef = useRef(null);
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play(),
pause: () => videoRef.current.pause(),
seek: (seconds) => {
videoRef.current.currentTime = seconds;
},
}), []);
return <video ref={videoRef} src={src} width={480} />;
});
The third argument is a dependency array, just like useEffect. The handle object is recreated only when a listed dependency changes; an empty array means it is built once. Most handles depend on nothing, so [] is common and correct.
A scrollable list example
Imperative APIs shine when a parent must trigger a one-off action on a child’s internal DOM that the parent cannot otherwise reach.
import { forwardRef, useImperativeHandle, useRef } from "react";
const ItemList = forwardRef(function ItemList({ items }, ref) {
const listRef = useRef(null);
useImperativeHandle(ref, () => ({
scrollToItem(index) {
const node = listRef.current.children[index];
node?.scrollIntoView({ behavior: "smooth", block: "center" });
},
}), []);
return (
<ul ref={listRef} style={{ maxHeight: 200, overflowY: "auto" }}>
{items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
);
});
A parent can now call listRef.current.scrollToItem(42) without knowing anything about the list’s markup.
When it is justified
Reach for useImperativeHandle only when declarative options fall short. The table below contrasts the two approaches.
| Situation | Prefer declarative | Prefer imperative handle |
|---|---|---|
| Showing/hiding content | state + conditional render | — |
| Focusing a field after an action | — | focus() |
| Toggling a value the UI reflects | controlled prop | — |
| Triggering scroll, play, animation | — | scrollTo(), play() |
| Passing data down | props | — |
If you can express the behavior as state flowing through props, do that first. Imperative handles are a last resort for genuine actions, not a shortcut around data flow.
Best Practices
- Expose the smallest possible API—name methods after actions (
focus,reset,play) rather than returning raw DOM nodes. - Always pair
useImperativeHandlewithforwardRef(or React 19’srefprop) so the child can receive the parent’s ref. - Provide a dependency array; use
[]when the handle never changes to avoid rebuilding it each render. - Prefer declarative props and state for anything the UI reflects; reserve imperative handles for true one-off actions.
- Guard internal DOM refs against
nullinside handle methods, since they may run before mount completes. - Document the handle’s shape (or type it with TypeScript) so consumers know exactly what they can call.