🎯 Imperative Handles in React 19
In React, we usually build UIs in a declarative way. But sometimes, you may want to imperatively interact with a child component—for example:
To do this, React allows you to expose methods from a child component to the parent using a ref. This ref is created with useRef and passed down to the child. Once inside the child, you can attach methods to it.
reftype InputAPI = { focusInput: () => void }function MyInput({ref,...props}: React.InputHTMLAttributes<HTMLInputElement> & {ref: React.RefObject<InputAPI>}) {const inputRef = useRef<HTMLInputElement>(null)ref.current = {focusInput: () => inputRef.current?.focus(),}return <input ref={inputRef} {...props} />}function App() {const myInputRef = useRef<InputAPI>(null)return (<div><MyInput ref={myInputRef} placeholder="Enter your name" /><button onClick={() => myInputRef.current?.focusInput()}>Focus the input</button></div>)}
✅ This works, but has limitations:
useImperativeHandleReact provides a built-in hook—useImperativeHandle—to safely expose imperative methods. In React 19, you can now access ref directly as a prop, so there’s no need for forwardRef.
type InputAPI = { focusInput: () => void }function MyInput({ref,...props}: React.InputHTMLAttributes<HTMLInputElement> & {ref: React.RefObject<InputAPI>}) {const inputRef = useRef<HTMLInputElement>(null)useImperativeHandle(ref,() => ({focusInput: () => inputRef.current?.focus(),}),[])return <input ref={inputRef} {...props} />}
Notice that we passed an empty dependency array to useImperativeHandle. That’s fine—even if you’re using refs inside—because React refs don’t change between renders, so they don’t need to be included in dependencies.
📘 Learn more: Why you shouldn’t put refs in a dependency array
useImperativeHandle is useful for:
⚠️ But keep in mind: imperative code should be a last resort. It’s harder to test, debug, and reason about. Prefer declarative solutions when possible.
📖 More on that here: Imperative vs Declarative Programming
useImperativeHandle lets child components expose custom methods to their parent.ref can be accessed directly from props, so there’s no need to use forwardRef.