Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate React 16.8+ ~22 min· topic 4 of 6

Topic 8.4

useImperativeHandle: Exposing a Small API

In one line

useImperativeHandle(ref, () => ({ ...methods }), deps) makes a parent's ref point at an object you choose instead of a DOM node, so you expose a few safe methods such as focus, scrollToBottom or reset and keep the rest of your component private.

Think of it like this

A TV remote. The TV has hundreds of parts inside, but the remote gives you only a few buttons: power, volume, channel. You can't reach in and rewire the screen. useImperativeHandle builds a remote control for your component: the parent gets a small object with a few buttons (methods) and nothing else.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

useImperativeHandle
A hook that sets what a parent's ref points to: an object you build, not a DOM node.
Handle
The object the parent receives in ref.current, usually with a few methods.
Imperative
Giving step-by-step commands, like video.play(). The opposite of describing a result with props.
createHandle
The function you pass to the hook. It returns the handle object.
Internal ref
A ref the component keeps for itself, pointing at its own DOM nodes, never given to the parent.
API
Application programming interface: the list of things other code is allowed to call on your code.

Step by step

01Start from a forwarded ref

This component hands the parent its whole <input>. The parent could do anything with it, even ref.current.remove().

SearchBox.jsxwhole filejsx
function SearchBox({ ref }) {
  return <input ref={ref} type="search" />;
}

02Keep the DOM node to yourself

Give the input your own internal ref. The ref prop from the parent is no longer attached to any element.

SearchBox.jsxwhole filejsx
function SearchBox({ ref }) {
  const inputRef = useRef(null); // private
  return <input ref={inputRef} type="search" />;
}

03Expose only what the parent needs

Call useImperativeHandle with the parent's ref and a function that returns a small object. The methods use the internal ref. Pass [] as dependencies, because the methods only use the stable ref.

SearchBox.jsxwhole filejsx
function SearchBox({ ref }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current.focus();
    },
    clear() {
      inputRef.current.value = "";
    },
  }), []);

  return <input ref={inputRef} type="search" />;
}
Expose only what the parent needsdiagram
Rendering diagram…

04Call the methods from the parent

The parent uses searchRef.current.focus() and searchRef.current.clear() in its handlers. searchRef.current.style is undefined: the DOM node isn't exposed.

05Ask: could this be a prop?

Before adding a method, check whether a prop would do. Opening, closing, selecting and filtering are state the parent can pass down. Focus, scroll and media playback are DOM actions with no prop equivalent, which is where handles belong.

Ask: could this be a prop?diagram
Rendering diagram…

Try it yourself

  1. 1

    Add a method

    In the first example, add select() to the handle (it calls inputRef.current.select()) and a Select all button in App that uses it.

  2. 2

    Drop the dependency array

    Remove the [] from useImperativeHandle in the chat log and add a console.log("handle created") inside createHandle. Add a state counter to App that re-renders it. How often is the handle created now? (After every render of ChatLog.)

  3. 3

    Turn a method into a prop

    Imagine HelpPanel had open() and close() methods. Write down what the parent's JSX would look like with them, then compare with the prop version. Which one shows you, just by reading, when the panel is visible?

Code & diagrams

Expose focus and clear, hide the DOM node New tab

The parent's ref holds `{ focus, clear }`, not the input. It can clear and focus the box, but it can't touch styles or remove the node. (The second small root at the bottom exists only to peek at the handle.)

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

value now: "" | focused: true
handle keys: [ 'focus', 'clear' ] | style: undefined
A chat log the parent can scroll and reset New tab

Scrolling is a DOM action with no prop, so a small handle is a good fit. The messages themselves still arrive as a prop.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

at the bottom: true
at the top: true
When not to use it: open and close with a prop New tab

Opening is just data: `isOpen`. Anyone reading `App` sees when the panel shows, DevTools shows the state, and the panel can't get out of sync with the parent. An `open()` method on a handle would hide all that.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

HelpPanel rendered, isOpen = false
HelpPanel rendered, isOpen = true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Call a method that isn't exposed

In the first example, change the Clear button to call searchRef.current.select().

terminal
$ Console (on click)
── what you'll see ──
Uncaught TypeError: searchRef.current.select is not a function

Break #2

Use a value from an old render inside the handle

Add const [count, setCount] = useState(0) to a component and expose getCount() { return count; } with the dependency array []. Increase the count, then call getCount() from the parent.

terminal
$ Console
── what you'll see ──
getCount() always returns 0, however many times the count went up.

Myth vs fact

Myth

useImperativeHandle is how a parent controls a child.

Fact

Props are how a parent controls a child. A handle is for the few DOM-like commands props can't express.

Myth

The handle replaces forwarding the ref.

Fact

You still receive the ref (as a prop in React 19, or from forwardRef) and pass it to the hook. The hook decides what the ref points to.

Myth

The handle is created during render.

Fact

React calls createHandle during the commit, when refs are attached, and sets the ref to null when the component unmounts.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Internally useImperativeHandle is a layout effect that sets ref.current = createHandle() (or calls a callback ref with it) and clears it on cleanup. That's why it follows the dependency rules of effects and why the exhaustive-deps lint rule checks it.

  • ▸

    Handles can pass through several layers: a DatePicker can expose focus() that calls the handle of an inner TextField, which calls the DOM. Each layer keeps its own private details.

  • ▸

    In TypeScript, describe the handle with an interface and type the ref with it: useRef<SearchBoxHandle>(null) in the parent and ref?: React.Ref<SearchBoxHandle> in the child's props (Phase 12).

  • ▸

    Libraries such as rich text editors, video players and virtualised lists commonly expose handles (editor.focus(), list.scrollToIndex(500)), because those actions really are commands. Data still flows through props.

Remember this

  1. 1

    In Topic 8.3 a component forwarded its ref to a DOM element, so the parent got the whole node: it could focus it, but also change its styles, remove its children or read its value. Sometimes that is too much power. useImperativeHandle lets you decide exactly what the parent's ref.current will be.

  2. 2

    Imperative means "giving a command": player.play(), input.focus(). React prefers declarative props (<Player playing={true} />). The word in the hook's name is a hint: you are adding commands to a component, which should be the exception.

  3. 3

    The signature is useImperativeHandle(ref, createHandle, dependencies?). ref is the ref your component received as a prop (React 19) or from forwardRef. createHandle is a function returning the object you want to expose, usually methods that use your own internal refs. dependencies works like an effect's: React re-creates the handle when one changes; leave it out and the handle is rebuilt after every render.

  4. 4

    When React sets it: during the commit, at the same time refs are attached (before layout effects), React calls createHandle and puts the result in ref.current. On unmount it sets ref.current back to null. So, like any ref, read it in event handlers and effects, not during render.

  5. 5

    Good uses: methods that match real DOM actions you can't express as props: focus(), blur(), scrollIntoView(), scrollToBottom(), select(), playing and pausing media, or resetting an uncontrolled form. Each method is small and calls a method on an internal DOM ref.

  6. 6

    When not to use it: if something can be a prop, make it a prop. Instead of modalRef.current.open(), write <Modal isOpen={isOpen} />. Instead of listRef.current.setItems(...), pass items. Props keep the data flow one-way and visible: anyone reading the parent's JSX sees what the child shows. Imperative handles hide state changes inside method calls that React and DevTools don't track.

Explain it without notes

01

What does useImperativeHandle do and why would you use it instead of forwarding a ref to a DOM node?

02

Explain each argument of useImperativeHandle(ref, createHandle, deps).

03

When should you not use useImperativeHandle? Give an example and the better alternative.

Practice

01

Write a VideoBox component that exposes only play() and pause() to its parent, using an internal ref on a <video> element.

02

Write an uncontrolled NoteForm (inputs with defaultValue) that exposes reset(), which empties every field and focuses the first one.

Trade-offs

  • ↔

    A handle keeps your DOM private and your API small, but method calls are invisible in the JSX and in DevTools. Use it for commands, keep data in props.

  • ↔

    Handles with many methods often mean state is in the wrong component. If the parent calls setX, setY, getZ on a child, lift that state up instead (Topic 6.1).

Done when you can

  • I can expose a small handle with useImperativeHandle using an internal ref.

  • I can explain what the parent's ref.current holds when a handle is used.

  • I can choose the right dependency array for a handle.

  • I can tell when a prop is better than an imperative method.

  • I know React sets the handle during the commit and clears it on unmount.