Good to knowAdvanced React APIs · 05

useImperativeHandle

Expose a small set of methods (focus, clear) on a ref, for commands that don’t fit in state.

On this page · 7 sections
  1. In one minute
  2. The example: “Apply coupon”
  3. How it works
  4. Common mistakes
  5. Interview Q&A
  6. Related
  7. Sources

In one minute

A parent can pass a ref to a component. By default, if the component puts that ref on an <input>, the parent gets the whole DOM node. useImperativeHandle(ref, createHandle, deps) lets the component hand back its own object instead, with only the methods it chooses, like focus() and clear(). It’s for commands: “focus now”, “scroll now”, “play now”. Those are things that happen once at a moment, not facts the UI should keep showing, so they fit badly in state and props. In React 19, ref is a normal prop for function components, so you don’t need forwardRef.

You’ll be able to: expose a small imperative API from a component, and explain why a “shouldFocus” prop doesn’t work.

A shouldFocus prop
Checkout.tsx
1function CouponField({ shouldFocus }) {2  const inputRef = useRef(null)3  useEffect(() => {4    if (shouldFocus) inputRef.current.focus()5  }, [shouldFocus])6  return <input ref={inputRef} />7}89function Checkout() {10  const [shouldFocus, setShouldFocus] = useState(false)11  return (<>12    <CouponField shouldFocus={shouldFocus} />13    <button onClick={() => setShouldFocus(true)}>Apply coupon</button>14  </>)15}
What you see
Coupon code
Apply coupon
Focus: –
  1. event<button onClick={() => setShouldFocus(true)}>Apply coupon</button>Click 1: the flag goes from false to true.
  2. effectsif (shouldFocus) inputRef.current.focus()The dependency changed, so the effect runs and focuses the field.
  3. event<button onClick={() => setShouldFocus(true)}>Apply coupon</button>The user clicks elsewhere, then click 2. The flag is already true: same value, no re-render, no effect.
  4. effectsRecorded: nothing happened on click 2, and focus stayed on the button. A command doesn’t fit in state.

0 / 0
“Apply coupon” with an empty field should put the cursor in the coupon field, every time. It’s clicked twice, with a click elsewhere in between (both versions recorded).

The example: “Apply coupon”

At checkout, clicking “Apply coupon” with an empty field should put the cursor back in the coupon field, every time.

1. With a prop

function CouponFieldFlag({ shouldFocus }: { shouldFocus: boolean }) {
	const inputRef = useRef<HTMLInputElement>(null)
	useEffect(() => {
		if (shouldFocus) inputRef.current!.focus()
	}, [shouldFocus])
	return <input id="coupon" ref={inputRef} placeholder="Coupon code" onFocus={() => log('  coupon field focused')} />
}

function CheckoutFlag() {
	const [shouldFocus, setShouldFocus] = useState(false)
	return (
		<>
			<CouponFieldFlag shouldFocus={shouldFocus} />
			<button id="apply" onClick={() => setShouldFocus(true)}>Apply coupon</button>
		</>
	)
}

Click “Apply coupon”, click elsewhere on the page, then click it again:

  coupon field focused

The second click logged nothing:

[]

shouldFocus was already true, so setShouldFocus(true) changed nothing: no render and no effect. To make it work, you’d have to reset the flag after focusing, or bump a counter (setFocusRequest(n => n + 1)) just to make the effect run again. Both are state that exists only to fake a function call.

2. With useImperativeHandle

type CouponHandle = { focus: () => void; clear: () => void }

function CouponField({ ref }: { ref: Ref<CouponHandle> }) {
	const inputRef = useRef<HTMLInputElement>(null)
	useImperativeHandle(
		ref,
		() => ({
			focus: () => inputRef.current!.focus(),
			clear: () => (inputRef.current!.value = ''),
		}),
		[],
	)
	return <input id="coupon" ref={inputRef} placeholder="Coupon code" onFocus={() => log('  coupon field focused')} />
}

function Checkout() {
	const couponRef = useRef<CouponHandle>(null)
	return (
		<>
			<CouponField ref={couponRef} />
			<button id="apply" onClick={() => couponRef.current!.focus()}>Apply coupon</button>
		</>
	)
}

Same two clicks:

  coupon field focused
  coupon field focused

Both clicks focused the field, with no state and no render.

3. What the parent can reach

const handle = couponRef.current as unknown as Record<string, unknown>
log('keys:', Object.keys(handle))
log('handle.value:', String(handle.value), '· handle.style:', String(handle.style))
log('is it the <input>?', handle instanceof HTMLInputElement)
keys: ["focus","clear"]
handle.value: undefined · handle.style: undefined
is it the <input>? false

The parent gets exactly focus and clear. It can’t read .value, change .style or call anything else on the input. The component can change its markup later without breaking the parent.

How it works

State or a command?

Ask: “does this change what’s shown?” If yes, it’s state or props, like isOpen, value or selectedId. If it’s “do this once, now” (focus, scroll into view, play, shake, validate and report), an imperative method is the honest model. Libraries do this too: form libraries expose setFocus(), media players seekTo(), chart wrappers resetZoom().

Common mistakes

Interview Q&A

What does useImperativeHandle do?

It sets the parent’s ref to an object you create, instead of a DOM node. useImperativeHandle(ref, () => ({ focus() {…} }), deps) gives the parent only the methods you list.

Why not just a shouldFocus prop?

Because focusing is a command, not a state. Recorded: the second “Apply coupon” click did nothing, because setShouldFocus(true) on a value that was already true causes no render and no effect. The imperative version focused on both clicks.

How does a function component receive a ref in React 19?

As a regular ref prop. Before React 19 you had to wrap the component in forwardRef.

What can the parent do with the handle?

Only what’s in it. Recorded: Object.keys(handle) was ["focus", "clear"], handle.value and handle.style were undefined, and it wasn’t an HTMLInputElement.

When would you use it in real code?

Focusing or validating form fields, scrolling a message list to the bottom, play/pause/seek on a media wrapper, wrapping a non-React library (a map, a chart, an animation) behind a few methods.

Sources

Primary source: react.dev/reference/react/useImperativeHandle