skip to content

What does useImperativeHandle(ref, createHandle, deps) do in a React component, and why expose a custom handle instead of the raw DOM node?

level: middleimportance: should knowfreq 40%

answer

  1. it decides what ref.current points at
  2. two refs: one internal, one exposed
  3. narrow surface beats whole DOM node
  4. deps rebuild the handle, not the ref
  5. escape hatch — try a prop first

basics

~20 s

useImperativeHandle replaces what the parent's ref points at with the object your createHandle function returns. The parent then sees only the methods you chose to expose, instead of the underlying DOM node or anything else inside the child.

solid answer

~50 s

You call it inside the child, passing the `ref` you received (a prop in React 19, `forwardRef`'s second argument in older code), a `createHandle` function returning the object the parent should get, and an optional dependency array. React sets `ref.current` to that object, so `parentRef.current.focus()` calls *your* method rather than reaching into a DOM node. The dependency array works like the other hooks': the handle is rebuilt when a listed value changes, and if you omit the array it is rebuilt after every render. The reason to bother is API narrowing. Forwarding the root `<input>` hands callers the entire `HTMLInputElement` — they can set `value`, mutate classes, read layout — and freezes your markup, since wrapping the root breaks them. A handle of `{ focus, clear }` is a contract you can keep while the internals change.

code

javascript · 25 lines
javascript
import { useImperativeHandle, useRef } from 'react';

function SearchField({ ref, ...rest }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => {
      inputRef.current.value = '';
      inputRef.current.focus();
    },
  }), []);

  return <input ref={inputRef} type="search" {...rest} />;
}

export function Toolbar() {
  const searchRef = useRef(null);
  return (
    <>
      <SearchField ref={searchRef} />
      <button onClick={() => searchRef.current.clear()}>Clear</button>
    </>
  );
}

go deeper

for a junior

Know the shape: called inside the child, it makes the parent's ref point at an object of your choosing, typically something like { focus }. Be able to say it is used rarely, for actions props cannot express.

for a middle

Explain the three arguments and the two-ref structure — an internal ref on the real node, the incoming ref replaced by your handle — and what the dependency array actually rebuilds.

for a senior

Argue the encapsulation case: exposing the root node freezes your markup and lets callers mutate DOM React owns. Show where you draw the line between a prop, a callback prop, and a handle method in a shared component.

for a principal

Treat it as public API design for a component library: every handle method is a contract you must keep across versions. Be ready to discuss how you review, document and version these escape hatches so they do not become the default integration path.

## The signature ```jsx useImperativeHandle(ref, createHandle, dependencies?) ``` - **`ref`** — the ref the parent gave you. In React 19 that is a normal prop you destructured; in pre-19 code it is the second parameter supplied by `forwardRef`. - **`createHandle`** — a function taking no arguments and returning the object you want `ref.current` to be. Whatever it returns is what the parent gets. - **`dependencies`** — optional array. If any listed value changes between renders, React discards the old handle and calls `createHandle` again. Omit the array entirely and the handle is rebuilt after every render. Without the hook, a ref forwarded onto `<input ref={ref} />` points at the DOM element. With the hook, React ignores that association for the parent's ref and points it at your object instead. ```jsx function FancyInput({ ref, ...rest }) { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; }, }), []); return <input ref={inputRef} {...rest} />; } ``` Note the two refs. The internal `inputRef` still holds the real node so your methods have something to act on; the external `ref` holds the curated handle. Those are separate jobs and conflating them is the usual first mistake. ## Why narrow at all Forwarding the root node is a wide promise. `HTMLInputElement` exposes hundreds of properties and methods, and every one of them becomes something a caller may already be using by the time you want to change your markup. Three concrete costs: 1. **Encapsulation.** A caller can set `value` or toggle classes directly, mutating DOM that React believes it owns. The next render can quietly stomp those mutations, producing bugs that reproduce only in certain orders. 2. **Refactoring freedom.** The day you wrap the input in a container for an adornment or a validation icon, every caller that assumed `ref.current` was an input breaks. With a handle, you change one function body. 3. **Discoverability.** `{ focus, clear, scrollIntoView }` documents itself and types cleanly. "It is whatever the root element happens to be" documents nothing. ## What belongs in a handle The honest test: could this be a prop instead? Most things can. Telling a child *what to display* is a prop. Telling a parent *that something happened* is a callback prop. Sharing values with distant components is context or a store. What genuinely resists a declarative expression is a one-shot, non-idempotent action tied to a moment in time: - `focus()` and `select()` after a validation failure - `scrollIntoView()` on a row a user just jumped to - `play()` / `pause()` on a media element - `reset()` on an uncontrolled widget - `open()` / `close()` on a dialog whose animation state you own If your handle grows getters like `getValue()`, that is usually the design telling you the value should have been lifted or reported through a callback prop instead. ## The dependency array The array does not control *when the parent may call* your methods; it controls *when the handle object is rebuilt*. Because the methods you return usually close over refs and props, an empty array is common and safe when every method reads through a ref rather than capturing a value directly. If a method closes over a prop or a piece of state, that value belongs in the array, exactly as it would for `useMemo` or `useCallback` — otherwise the parent keeps calling a method that closed over an outdated value. Omitting the array rebuilds every render, which is rarely what you want but is not incorrect: the parent re-reads `ref.current` each time it calls. ## Relationship to the ref prop `useImperativeHandle` does not create a ref or decide who has one. The parent still decides to pass a ref; the hook only decides *what that ref points at*. If no parent passed one, `ref` is `undefined` and the hook is a no-op — you do not need to guard it. And a component may still keep its own internal refs to real nodes; the hook governs only the ref that came in from outside. ## Use it sparingly Every imperative handle is an escape hatch out of React's data flow. It is the right tool a few times in a component library and almost never in application code. Interviewers ask about it partly to see whether you reach for it eagerly — the strong answer starts with "first I check whether a prop expresses it".

  • What changes if you omit the dependency array entirely?
    React rebuilds the handle after every render instead of only when a listed value changes. That is wasteful but not broken, since the parent reads `ref.current` at call time. The genuinely broken case is the opposite: an empty array around a method that captured a prop, which then keeps acting on a stale value.
  • Your handle is growing a getValue() method. What does that suggest?
    That the value is really shared state living in the wrong place. A parent pulling data out imperatively cannot re-render when it changes, so it will read at the wrong moments. Lift the value or report it through a callback prop, and keep the handle for actions that cannot be expressed declaratively.
  • Do you still need forwardRef to use useImperativeHandle in React 19?
    No. Destructure `ref` from props and pass that to the hook. `forwardRef` still works for existing code, but the hook only needs a ref object — it does not care how the component obtained it.

saying these in an interview costs you the question

  • Says it creates a ref for the parent
  • Passes the same ref both to the hook and to the DOM element
  • Uses it to hand state up instead of a callback prop
  • Thinks the deps array controls when methods may be called
  • Treats an imperative handle as the normal way to talk to a child

context