Back to all notes

useImperativeHandle : Expose Methods from Child to Parent

When refs aren’t enough, selectively expose imperative methods from a child component.

2 min read

Every so often I need a parent to call a method on a child, usually to focus an input. Data flowing down and events flowing up doesn’t cover that. The usual fixes are threading callback props through several layers or toggling state to force the focus, and useImperativeHandle is the cleaner escape hatch: the child decides exactly which methods its ref exposes.

The autoFocus workaround

This version flips a shouldFocus state and feeds it to autoFocus:

import { useState } from "react";

function App() {
  const [shouldFocus, setShouldFocus] = useState(false);
  return (
    <div>
      <h1>useImperativeHandle</h1>
      <input autoFocus={shouldFocus} placeholder="Click button to focus me" />
      <button onClick={() => setShouldFocus(true)}>Focus Input</button>
    </div>
  );
}

export default App;

I don’t like it. Focusing costs an extra render, and the trick doesn’t reuse across different inputs. It also breaks when the component structure changes.

One exposed method

The custom input below exposes exactly one thing, a focusInner method, using forwardRef plus useImperativeHandle:

import { useRef, useImperativeHandle, forwardRef } from "react";

const CustomInput = forwardRef(function CustomInput({ ...rest }, ref) {
  const localRef = useRef();
  useImperativeHandle(
    ref,
    () => ({
      focusInner() {
        localRef.current.focus();
      },
    }),
    [],
  );
  return <input ref={localRef} {...rest} />;
});

function App() {
  const inputRef = useRef();
  const handleFocus = () => {
    inputRef.current.focusInner();
  };
  return (
    <div>
      <h1>useImperativeHandle</h1>
      <CustomInput ref={inputRef} placeholder="Click button to focus me" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

export default App;

Inside the child, a local ref sits on the real <input>. useImperativeHandle puts a single focusInner method on the parent’s ref, and the parent just calls inputRef.current.focusInner(). No state toggles, no extra renders.

Expose only what gets called

The exposed object is an API, so I keep it minimal; no leaking internals the parent doesn’t call. I don’t mix controlled inputs with imperative APIs either; one approach per component. Props and composition come first, and an imperative handle is the last resort.