Difficulty: Advanced
What is useImperativeHandle and when would you use it with forwardRef?
Let me break this down because useImperativeHandle and forwardRef are two of those React features that sound intimidating but solve a really practical problem once you get them.
First, let us talk about the problem. In React, refs are normally used to get a direct reference to a DOM element -- like an input field so you can call .focus() on it. But what happens when you wrap that input in a custom component? If a parent component passes a ref to your custom FancyInput component, React does not know what to do with it by default because refs are not regular props -- they get special treatment and are not forwarded automatically.
That is where forwardRef comes in. It is essentially React saying: hey, I will let you receive this ref that a parent is passing down and decide what to do with it. You wrap your component in forwardRef, and now you get access to the ref as the second argument (after props). Most of the time, you just attach it to a DOM element inside your component, and the parent gets direct access to that DOM node.
But here is where useImperativeHandle gets interesting. Sometimes you do NOT want the parent to have full access to the raw DOM node. Think about it -- if you are building a reusable component library, do you really want consumers poking around with every property and method on the underlying input element? They could mess with styles, attributes, or call methods that break your component's internal state. It is like giving someone the keys to your entire house when they only need access to the front door.
useImperativeHandle lets you customize exactly what the parent sees when they use the ref. Instead of exposing the entire DOM node, you create a controlled API. You might expose a focus() method, a clear() method, and a getValue() method, and that is it. The parent can call inputRef.current.focus() or inputRef.current.clear(), but they cannot reach in and do inputRef.current.style.display = 'none' or some other dangerous operation.
Let me give you a real-world scenario. Say you are building a form with a custom OTP input component -- you know, those 4-6 individual digit boxes. Internally, you have multiple input elements, focus management logic, and validation. But the parent form just needs to call otp.current.getValue() to get the complete code, or otp.current.reset() to clear all boxes. useImperativeHandle is perfect for this. You hide all the complexity and expose a clean, documented API.
Another common use case is media players. You might have a VideoPlayer component that wraps an HTML video element with custom controls, progress bars, and buffering logic. The parent might need to call player.current.play(), player.current.pause(), or player.current.seekTo(30). But you do not want the parent directly accessing the video element because that could conflict with your custom controls.
The syntax is straightforward: inside your forwardRef component, you call useImperativeHandle(ref, () => ({ ...methods }), deps). The second argument is a factory function that returns an object -- that object becomes what the parent sees on ref.current. The third argument is a dependency array, just like useEffect. If it is empty, the exposed API is created once and stays stable.
Now, a really important note about React 19: forwardRef is being deprecated. In React 19, ref becomes a regular prop, so you can just accept it like any other prop without the forwardRef wrapper. You will still use useImperativeHandle to customize what the ref exposes, but the forwardRef ceremony goes away. Knowing this shows interviewers you are keeping up with the React roadmap.
Here is the golden rule though: prefer declarative props over imperative refs. If you can solve the problem by passing a prop like isFocused={true} or value and onChange, do that instead. Imperative access via refs should be the exception, not the rule. The React mental model is declarative -- you describe what the UI should look like, and React figures out how to get there. Refs are an escape hatch for when you genuinely need to reach outside that declarative model, like focusing an input, triggering an animation, or integrating with a non-React library.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
// Expose only what the parent needs
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = '';
inputRef.current.focus();
},
getValue() {
return inputRef.current.value;
}
}), []); // empty deps - stable reference
return (
<input
ref={inputRef}
className="fancy-input"
{...props}
/>
);
});
// Parent usage
function Form() {
const inputRef = useRef(null);
function handleSubmit() {
const value = inputRef.current.getValue();
console.log('Submitted:', value);
inputRef.current.clear();
}
return (
<>
<FancyInput ref={inputRef} placeholder="Type here..." />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={handleSubmit}>Submit</button>
</>
);
}
useImperativeHandle limits the ref to { focus, clear, getValue }. The parent cannot accidentally access internal DOM properties.
useImperativeHandle, forwardRef, Ref API, Expose Methods