What does forwardRef do, and when would you reach for useImperativeHandle
`forwardRef` forwards a ref through a component so parents can access the underlying DOM node or imperative API. `useImperativeHandle` customizes what the ref exposes — handy when you need to expose specific methods (`focus`, `scrollIntoView`, `open()`) without leaking the DOM node. In React 19, regular function components accept ref as a prop, removing the need for forwardRef.
Two related APIs around imperative escape hatches in a mostly-declarative library.
forwardRef
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
));Without forwardRef, a parent passing ref to <Input> would attach to the component instance (function components have no instance, so it'd be null). forwardRef lets the ref reach the DOM node.
Use it on leaf primitives that wrap a single DOM element — Button, Input, Textarea, MenuItem.
React 19 update
React 19 lets regular function components accept ref as a regular prop:
function Input({ ref, ...props }: Props & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}forwardRef is being deprecated. New code in React 19+ should use prop-style refs.
useImperativeHandle
When you don't want to expose the DOM node directly but a curated API:
type DialogHandle = { open: () => void; close: () => void };
const Dialog = forwardRef<DialogHandle, Props>((props, ref) => {
const [open, setOpen] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}), []);
return open ? <DialogShell ...> ... </DialogShell> : null;
});
// Parent
const dialogRef = useRef<DialogHandle>(null);
dialogRef.current?.open();The ref now exposes open() / close() instead of the DOM node — encapsulated imperative API.
When to use which
| Need | Use |
|---|---|
| Pass-through ref to a DOM node | forwardRef (or ref prop in React 19) |
| Expose custom methods | useImperativeHandle |
| Coordinate work across components | Lift state up; rarely refs |
When to avoid
- Don't reach for imperative APIs to do what state can do.
<Dialog open={isOpen}>is cleaner thanref.current.open(). - Don't use
useImperativeHandleto leak internals. If you expose 10 methods, the component is doing too much.
Use cases that legitimately need imperative handles
- Animations that don't fit declaratively (focus management after a transition).
- Third-party integration (a Stripe payment form ref that exposes
submit()). - Library components with measure / scrollIntoView / focus methods.
Gotcha: ref + memo
Until React 19, you needed forwardRef inside memo() in a specific order. In 19, both go away as constraints.
Interview framing
"forwardRef lets a parent ref reach through your component to the underlying DOM node — typically for design-system primitives. useImperativeHandle lets you expose a curated set of methods (focus, open, scrollIntoView) instead of the raw node. Use it when there's a legitimate imperative need — animation, focus orchestration, third-party widget control — but prefer declarative props for state-driven behavior. In React 19, function components accept ref as a regular prop, so forwardRef is being deprecated; useImperativeHandle still works the same."
Follow-up questions
- •When have you used useImperativeHandle in production?
- •How does React 19 change ref forwarding?
- •Why prefer props over refs for state?
Common mistakes
- •Reaching for refs when state would work.
- •Exposing the whole DOM node when only one method is needed.
- •Forgetting forwardRef and silently dropping refs (pre-19).
Performance considerations
- •No measurable cost. Just a discipline issue: imperative escape hatches hide data flow.
Edge cases
- •Strict Mode double-invocation interacting with imperative handles.
- •Cleanup on unmount when handle holds references.
- •TypeScript ref typing — Ref vs RefObject vs MutableRefObject.
Real-world examples
- •Radix primitives, MUI, react-hook-form Controller exposing focus(), video player libs.