DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on • Originally published at github.com

useImperativeHandle: how a child hands its parent a remote control (React 19, no forwardRef)

React is declarative: you set state, React updates the DOM. But some things are stubbornly imperative — focus this input, play that video, scroll here, run an animation. useImperativeHandle is the sanctioned way to do them: a child hands its parent a small remote control. I built an interactive demo where a parent drives two children entirely through refs.

▶ Live demo: https://useimperativehandle.pages.dev/
Source: https://github.com/dev48v/useimperativehandle

The pattern

// child — React 19: ref is a normal prop
function FancyInput({ ref }) {
  const el = useRef(null);
  useImperativeHandle(ref, () => ({
    focus: () => el.current.focus(),
    clear: () => { el.current.value = ""; },
    shake: () => { /* toggle an animation class */ },
  }), []);
  return <input ref={el} />;
}

// parent — call those methods through the ref
const inputRef = useRef(null);
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>focus</button>
Enter fullscreen mode Exit fullscreen mode

useImperativeHandle(ref, createHandle, deps) sets what ref.current becomes: not the DOM node, but the object you return. In the demo the parent calls .focus(), .clear(), .shake() on the input and .play(), .pause(), .reset() on a little player — and a log shows each call as it happens.

Why not just forward the DOM node?

You could write <input ref={ref} /> and give the parent the real element. But then the parent can do anything — read .value, mutate styles, call .remove(). There's no contract, and the moment the child's internals change, callers break.

useImperativeHandle lets the child expose exactly the verbs it chooses — three methods, nothing else. It's the difference between handing someone the keys to your house and handing them a doorbell. Capabilities, not internals.

React 19 dropped the forwardRef ceremony

Before React 19 you had to wrap the child:

const FancyInput = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({ /* ... */ }));
  return <input ref={inner} />;
});
Enter fullscreen mode Exit fullscreen mode

In React 19, ref is just a regular prop — accept it and pass it straight to useImperativeHandle. No forwardRef needed. The hook itself is unchanged, so the same handle works either way.

When to use it — and the honest caveat

Good fits (imperative by nature, don't map to props):

  • focus() / select() on inputs
  • media: play() / pause() / seek()
  • scrollIntoView(), measuring layout
  • triggering a one-shot animation, opening/closing a <dialog>
  • resetting a complex third-party widget

But it's an escape hatch. If you find yourself calling child methods to keep state in sync, that's a smell — props, lifting state up, and context are the declarative tools that fit that job. Reach for useImperativeHandle when the action is a genuine one-time command ("do this now"), not a piece of shared state.

Click the buttons in the demo and watch the ref calls land in the log. If it clarified the imperative escape hatch, a star helps others find it: https://github.com/dev48v/useimperativehandle

Top comments (0)