useImperativeHandle
useImperativeHandle customizes the instance value exposed to parent refs: narrow APIs, when to avoid, and forwardRef pairing.
- react
- useimperativehandle
useImperativeHandle lets a child shape what a parent gets when it attaches a ref — instead of dumping the raw DOM node. Use it to expose a tiny command surface (focus, scrollToRow, reset) without leaking internal structure.
Docs: useImperativeHandle.
Pattern
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}), []);
return <input ref={inputRef} {...props} />;
});
function Parent() {
const ref = useRef(null);
return (
<>
<FancyInput ref={ref} />
<button type="button" onClick={() => ref.current?.focus()}>
Focus
</button>
</>
);
}
Parents cannot reach inputRef directly — only focus and clear. That encapsulation survives internal refactors (maybe the real node becomes a contenteditable).
When it is justified
| Use case | Why |
|---|---|
| Design system field | Focus without exposing DOM |
| Virtualized list | scrollToIndex |
| Media / map wrappers | play/pause/fitBounds |
| Legacy non-React widget | bridge imperative API |
If you only need the DOM node, forward the ref — skip useImperativeHandle.
Prefer declarative first
// Prefer
<Video playing={playing} onEnded={...} />
// Over
ref.current.play()
Imperative handles fight React’s model when overused: harder to test, harder to trace, easy to desync from props. Pair with controlled props for state; use the handle for actions that are inherently command-like.
TypeScript sketch
export type FancyInputHandle = {
focus: () => void;
clear: () => void;
};
const FancyInput = forwardRef<FancyInputHandle, Props>(function FancyInput(props, ref) {
// ...
});
Dependencies array
The create function rebuilds when deps change, similar to useMemo. Include values you close over. Empty deps if methods only use refs (refs are stable and .current is read at call time).
Interview out-loud
“useImperativeHandle customizes the value a parent receives from a ref so I expose a narrow command API instead of the raw DOM node. I use it sparingly for focus, scroll, and third-party bridges, and I prefer declarative props for ordinary state.”
Related on this site
Further reading
Production checklist
- Source of truth clear for every piece of UI state?
- Remount, route change, and Strict Mode cleanup paths handled?
- Urgent updates separated from deferrable work?
- Profiled before memo, virtualization, or context splits?
- Keyboard, focus, and accessible names still work after the change?
Edge cases worth rehearsing
Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.
Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.
When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.
Related guides
- useCopyToClipboard HookCopy text with navigator.clipboard, fallback execCommand, transient copied state, and permissions that fail in insecure contexts.
- useDebouncedValue HookDebounce a changing value in React without firing effects on every keystroke. Cleanup, leading vs trailing, and the stale timer that updates unmounted state.
- useEventListener HookAttach a window, document, or element listener with a stable handler ref so you do not rebind on every render. Capture option and target changes.
- useFetch With AbortControllerFetch in an effect with a signal, ignore aborted errors, and prevent the slow response from winning a race after the url changes.
- useIdle HookDetect idle users: reset a timer on input events, fire idle after timeout, and decide whether mount starts the timer immediately.