How useImperativeHandle works.
Used with forwardRef, useImperativeHandle(ref, createHandle, deps) lets a child customize the value the parent's ref points to — exposing a curated API ({ focus, reset }) instead of the raw DOM node. It runs during commit. It's an escape hatch — prefer declarative props when possible.
useImperativeHandle lets a child component control what a parent sees through a forwarded ref — instead of the parent getting the raw DOM node, it gets a custom object you define.
The mechanics
It's used with forwardRef:
const VideoPlayer = forwardRef((props, ref) => {
const videoRef = useRef();
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play(),
pause: () => videoRef.current.pause(),
reset: () => { videoRef.current.currentTime = 0; },
}), []); // deps — recreate the handle when these change
return <video ref={videoRef} src={props.src} />;
});
// parent:
const playerRef = useRef();
<VideoPlayer ref={playerRef} src="..." />;
playerRef.current.play(); // calls the CUSTOM handle, not the <video> nodeSignature: useImperativeHandle(ref, createHandle, deps):
ref— the forwarded ref from the parent.createHandle— a factory returning the object the parent'sref.currentwill point to.deps— recompute the handle when these change (like other hooks' deps).
How it works under the hood
During the commit phase, React calls createHandle() and assigns the result to ref.current (handling both object and callback refs). On unmount it clears the ref. It runs at commit time — before the parent's effects — so the parent can use the handle immediately. It's effectively a layout-timed effect that writes a custom value to the ref.
Why it exists
By default, forwardRef exposes the raw DOM node to the parent. useImperativeHandle lets you:
- Expose a curated, minimal API (
focus,scrollToTop,reset) instead of the whole DOM node — encapsulation. - Expose imperative actions on a component that has no single DOM node (a complex widget).
- Prevent the parent from doing arbitrary DOM manipulation on your internals.
The important caveat
It's an escape hatch. Most parent→child communication should be declarative — props and state. Reach for useImperativeHandle only for genuinely imperative actions that don't fit the data-flow model: focus management, triggering animations, media controls, scrolling. If you can express it as a prop, do that instead.
The framing
"With forwardRef, useImperativeHandle(ref, createHandle, deps) lets the child decide what the parent's ref points to — a curated object like { play, pause, reset } instead of the raw DOM node. React calls the factory during commit and writes it into ref.current, so the parent can call it immediately. It's for encapsulating a minimal imperative API, or exposing actions on a component with no single DOM node. But it's an escape hatch — declarative props are the default; use it only for inherently imperative things like focus, scroll, or media control."
Follow-up questions
- •Why must useImperativeHandle be used with forwardRef?
- •When does the handle get attached in React's lifecycle?
- •Why is useImperativeHandle considered an escape hatch?
- •What does the deps argument control?
Common mistakes
- •Using it without forwardRef.
- •Reaching for it when a declarative prop would work.
- •Omitting deps so the handle closes over stale values.
- •Exposing too much (the whole DOM node) instead of a minimal API.
Performance considerations
- •Negligible cost. The deps array matters for correctness (stale closures), not performance.
Edge cases
- •Parent passes a callback ref instead of an object ref.
- •deps change — the handle is recreated.
- •Component unmounts — ref.current is cleared.
- •Handle methods closing over stale state with empty deps.
Real-world examples
- •A custom input exposing focus()/select() to a parent form.
- •A modal or video player exposing open()/close() or play()/pause().