Back to React
React
medium
mid

How does the useImperativeHandle hook work in React, and when do you use it?

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.

4 min read·~6 min to think through

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:

jsx
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> node

Signature: useImperativeHandle(ref, createHandle, deps):

  • ref — the forwarded ref from the parent.
  • createHandle — a factory returning the object the parent's ref.current will 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().

Senior engineer discussion

Seniors explain the forwardRef pairing, the commit-time attachment, the encapsulation motivation (curated API, no-single-DOM-node case), and stress it's an escape hatch behind declarative props.

Related questions