useImperativeHandle: Exposing a Custom API from a Child Component
forwardRef passes a ref to a child so the parent can access its DOM node directly. useImperativeHandle goes further — it lets the child decide exactly what the parent can do with that ref, hiding internal implementation details.
When a parent passes a ref to a child component, it normally gets the raw DOM node back — every method, every property, full access. That is fine for simple cases like focusing an input. But for reusable components, exposing the entire DOM node is a leaky abstraction. The parent can call anything on it, including things it was never meant to call.
useImperativeHandle lets the child define exactly what the parent gets. Instead of the raw DOM node, the parent receives a custom object with only the methods the child explicitly exposes.
How It Works
The child uses forwardRef to receive the ref the parent passes, then uses useImperativeHandle to define what that ref contains:
import { forwardRef, useRef, useImperativeHandle } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
clearInput: () => { inputRef.current.value = ''; },
}));
return <input ref={inputRef} type="text" />;
});forwardRef wraps the component so it can receive a ref prop. useImperativeHandle takes that ref and a factory function that returns the object the parent will see in ref.current.
The internal inputRef is never exposed. The parent only sees focusInput and clearInput.
Using It from the Parent
The parent creates a ref and passes it as usual:
function Parent() {
const inputRef = useRef(null);
return (
<>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focusInput()}>Focus</button>
<button onClick={() => inputRef.current.clearInput()}>Clear</button>
</>
);
}inputRef.current is not the DOM input. It is the object returned by useImperativeHandle — only focusInput and clearInput. The parent cannot accidentally call inputRef.current.click() or read inputRef.current.value directly. The child controls the boundary.
Why This Pattern Exists
The core reason is encapsulation. The child keeps its internal workings hidden while letting the parent interact with it in a controlled, intentional way.
This matters most when building reusable components — design systems, component libraries, shared UI primitives. If CustomInput is used across twenty features and you later change how the internal input works, nothing breaks as long as focusInput and clearInput still behave correctly. The parent never had access to the internals.
The child defines the interface. The parent uses it. This is object-oriented encapsulation applied to React refs.
When to Use It
useImperativeHandle is rare in day-to-day application code. Most interactions between parent and child should happen through props and callbacks — the declarative React way. Refs and imperative handles are for cases where the parent genuinely needs to trigger a behavior in the child that cannot be expressed declaratively.
Reach for it when:
- Building a reusable input, modal, or media component that needs to expose specific imperative controls
- The parent needs to trigger something in the child without re-rendering (focus, scroll, play/pause)
- You want to explicitly limit what the parent can do with a child's internals
If you find yourself reaching for it in regular feature code, that is usually a signal to rethink the component API — props and callbacks can likely handle it.
Practice what you just read.
Keep reading