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.

June 7, 20263 min read2 / 2

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:

TSX
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:

TSX
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.

useImperativeHandle Quiz
1 exercise