React

useImperativeHandle: expose an API, not a ref

useImperativeHandle replaces a ref value with an object you choose, so the parent calls explicit methods and never receives the DOM node.

Key points

  • useImperativeHandle changes what the ref holds, not how the ref is passed.
  • Exposing two methods instead of the node shrinks the public surface and the number of couplings.
  • Since React 19 ref is a normal prop, so forwardRef is optional while the hook stays.

useImperativeHandle replaces a ref value with an object you choose: the parent calls explicit methods and never receives the DOM node. It pays off when the public API must stay narrower than the element's own.

What a ref really carries

A plain ref points at the element: whoever receives it can call focus, remove it from the DOM, rewrite its attributes. That power is wider than the component wants to hand out, and every method the parent uses becomes a coupling no type checks.

Two methods, on purpose

A text field that exposes the selection without handing over the input:

const TextField = forwardRef(function TextField({ label }, ref) {
  const inputRef = useRef(null);

  // The parent gets focus and selectAll, nothing else.
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
    selectAll: () => inputRef.current?.select(),
  }), []);

  return (
    
      {label}
      
    
  );
});

The parent gets two functions. It cannot read the value, it cannot swap the node, and the ref type can declare exactly those two methods instead of HTMLInputElement.

In React 19 ref is a prop

From version 19 ref arrives among the props, so forwardRef is no longer needed. The body of the hook stays identical and the caller does not change a line.

function TextField({ label, ref }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
    selectAll: () => inputRef.current?.select(),
  }), []);

  return (
    
      {label}
      
    
  );
}

When it is the wrong tool

If the parent only needs to measure the element, a plain ref is enough. If it needs to react to an event, a callback prop reads better. useImperativeHandle adds a layer: use it when the API must be narrower than the node, not to hide a ref nobody should hold.

The command

The package is on npm; the second command prints the resolved version, so the first example does not run on an old major.

npm install react@19 react-dom@19
npm ls react

Live records in the catalogue

Three published records with a narrow API: otp-input focuses one cell, dialog opens and closes without handing out the portal, expandable-tabs switches tab from outside. They are records among the 111,985 components in the catalogue.

Source and licence

React ships under the MIT licence. The reference page on react.dev calls useImperativeHandle an escape hatch and asks you to prefer the declarative route when one exists: that advice is the selection rule, not a footnote.

The limitation

The ref value is reassigned at commit, not during render: a parent that reads ref.current inside its own render sees the previous object, or null on the first pass. The hook does not fix that timing.

Where to continue

The direct comparison between ref and state is in ref DOM vs state; the calling rules for hooks are in rules of hooks; the case where a ref crosses the tree is in portals for modals.

Frequently asked questions

Does useImperativeHandle replace forwardRef?

No, it completes it: forwardRef forwards the ref to the component, useImperativeHandle decides what that ref holds. Since React 19 the first part is a prop and only the hook remains.

Why not pass the DOM node directly?

Because the node exposes dozens of methods and properties: whoever holds it can unmount it or rewrite its attributes. An object with two functions keeps the public API small and checkable.

Do the exposed functions stay stable between renders?

Only while the third argument of the hook holds still. With an empty dependency array the object is created once; with changing dependencies its identity changes on every render.

Does it work in a class component?

No. A class uses an object ref and assigns the methods in componentDidMount; the hook exists only inside functions.

Does an imperative API need a test?

Yes, a test that calls the method and checks the final result, not the object: with Testing Library you call ref.current.focus() and assert on document.activeElement.