Intro
In my day-to-day usage of React, there are two hooks that I use constantly; useState and useEffect. There are a few others I use fairly frequently as well, such as useCallback, useReducer, useMemo, and useRef. These six hooks represent the majority of my usage of the standard hooks provided by React, but there’s one hook that I’ve only ever come across twice in the last couple years I’ve been writing React; useImperativeHandle.
This post is going to give an overview of what useImperativeHandle does and why you might want to use it.
Manipulating the DOM with refs
Note
Proceed with caution when manually updating the DOM in React. In certain cases it's necessary due to limits on what React can do, but it should be seen as an escape hatch to be used judiciously.
To understand useImperativeHandle, you first need to have a foundational understanding
of refs, useRef and forwardRef, so we’ll cover those topics first. Feel free to skip ahead
to the next section if you already have a good understanding of those concepts.
There are numerous possible use cases for refs in React, but we’re going to cover one of the most commonly used ones. I’ll provide links in the resources section at the end of the post for details on other ways to use refs.
To motivate this example, suppose you have an input element that you want to focus when a button is clicked.

We have a basic component with an input and a button, and we want the input element to be focused when the button is clicked.
const FormInput = () => {
return (
<div>
<input />
<button>Focus</button>
</div>
);
};A JavaScript developer new to React might do something like this:
const FormInput = () => {
const handleClick = () => {
const input = document.querySelector("input");
input.focus();
};
return (
<div>
<input />
<button onClick={handleClick}>Focus</button>
</div>
);
};This works perfectly fine, but using a query selector always runs the risk of inadvertently selecting the wrong element. If you go with this approach, you’d need to ensure that the DOM node you’re going to manipulate has a unique identifier, which isn’t always easy to do in a large codebase. React provides a slightly more targeted way of accomplishing the same thing with refs. Let’s modify the example to use a ref and walk through the changes in detail.
const FormInput = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus</button>
</div>
);
};-
On line 2, we call the
useRefhook, which accepts a single argument and returns a value. The argument it accepts is the initial value for the ref, and the returned value is the ref itself. You can think of a ref as a container for something. Initially, the ref contains nothing, but we can tell TypeScript precisely what thing the ref will eventually contain, which in this case is anHTMLInputElement. -
We declare a
handleClickfunction on lines 4-8 that’s called when the button is clicked. This function checks to see if thecurrentproperty of the ref exists, and if so it calls thefocusfunction on it. Thecurrentproperty is what actually references a DOM node (or whatever the ref contains), and it’s the only property of a ref object. -
On line 12, we assign our ref to the
refprop of theinputelement. This is the critical piece that links the ref to the DOM node.
The advantage of using refs as opposed to query selectors is that we can guarantee that we’re targeting the correct element because we’re explicitly associating it with that element.
It’s critical to check that the current property of the ref is set before using it (like we’re doing in the first line of the handleClick function). If we were to immediately attempt to access the ref’s current property on line 3, it would return undefined because the element that uses the ref hasn’t been rendered yet.
Now that we’ve established a baseline level of knowledge about refs, let’s take it to the next level and explore how we can pass refs as props to other components.
Forwarding refs
Let’s make our example slightly more complex by refactoring our code. A common design pattern used in many React applications is to separate logic from the presentation layer. Let’s create a custom hook to encapsulate the logic of our input focus functionality.
const useFormInput = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return { inputRef, handleFocus };
};
const FormInput = () => {
const { inputRef, handleFocus } = useFormInput();
return (
<div>
<input ref={inputRef} />
<button onClick={handleFocus}>Focus</button>
</div>
);
};We’ve defined a custom hook called useFormInput and moved some logic from our FormInput component to that hook. Easy enough - everything should work exactly the same as before. But what if we need to call this hook from outside of the FormInput component? For instance, if the button was rendered in a different component than the input element it targets.
We’ll we’d just pass inputRef to FormInput as props, right? Here’s what that would look like:
const App = () => {
const { inputRef, handleFocus } = useFormInput();
return (
<div>
<FormInput ref={inputRef} />
<button onClick={handleFocus}>Focus</button>
</div>
);
};
const FormInput = ({ ref }: { ref: React.RefObject<HTMLInputElement> }) => {
return (
<div>
<input ref={ref} />
</div>
);
};Unfortunately, this doesn’t work. But we get a warning in the browser’s JavaScript console that provides some useful information.
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
What this is saying is that we cannot pass our ref into a prop called ref the same way you’d pass other props to a component. React has a dedicated mechanism for passing refs to custom components called forwardRef. We’ll update the FormInput definition to accept a forwarded ref then walk through the changes.
import { useRef, forwardRef } from "react";
// No changes to App or useFormInput
const FormInput = forwardRef((props: {}, ref: React.Ref<HTMLInputElement>) => {
return (
<div>
<input ref={ref} />
</div>
);
});The FormInput component should look almost identical to how it looked before. The only difference is that we’ve removed the ref from the prop definition and wrapped the component in a forwardRef function. forwardRef accepts a single parameter called render, and the argument you pass to that parameter is the component to which you’d like to pass a ref.
The return value of forwardRef is a slightly modified version of the component provided as an argument. It’s basically identical to the component provided as an argument, but there’s one key difference; it enables the component to accept a second argument (after the component props) for a ref!
It might be difficult to understand exactly what’s going on in the code above with the whole component definition passed as the argument to forwardRef. An alternative way to accomplish the same thing would be to refactor it as follows:
const FormInputInternal = (props: {}, ref: React.Ref<HTMLInputElement>) => {
return (
<div>
<input ref={ref} />
</div>
);
};
const FormInput = forwardRef(FormInputInternal);Now it should be a bit more clear that the only thing that changed in our original component definition is that we added a second parameter for the ref after the component props (we also changed the name, but this is just to avoid a collision with the FormInput component). Everything else stayed exactly the same. But if we tried to pass a ref to FormInputInternal, it wouldn’t work. We need to pass that component into forwardRef so that React can properly wire up the component to accept a ref.
The <ComponentName>Internal naming convention isn’t required, but it helps make clear that the component is not intended to be used directly. It’s an implementation detail that’s only intended to be used by the component that’s returned from forwardRef. When using this pattern, I typically define the components in a separate module and only export the component returned from forwardRef.
This might seem like a lot of ceremony to do something that seems like it should be simple. Hold onto that thought - we’re going to come back to it later on in the post. But first, we’re going to (finally) introduce useImperativeHandle.
useImperativeHandle
With refs and forwardRef, we’ve unlocked a ton of potential functionality that can help us avoid littering our code with query selectors. But this can be a double-edged sword. Consider a scenario where you’re building a third-party component library to be used by other developers, and this library has a component similar to the FormInput component we worked on in the previous section. We want to give consumers of the library the ability to focus on the input component programatically, so we use the exact same pattern applied in the previous example.
This approach successfully accomplishes the goal of enabling our users to focus on the element in whatever scenario they choose, but we’ve also enabled them to do anything to the DOM node underlying our input component. This completely breaks encapsulation and provides zero clarity on the “pre-approved” ways to manipulate the element without potentially breaking functionality.
So how can we find a reasonable middle ground where we give our users some ability to directly manipulate the underlying DOM element while more clearly defining the ways that users can do so without inadvertently breaking the component? Enter useImperativeHandle.
The React documentation describes useImperativeHandle as “a hook that lets you customize the handle exposed as a ref”. To understand that definition, it can be helpful to think of a ref as a generic object that has the ability to expose the functionality of whatever it references. It’s basically just a dumb proxy; it’ll expose all functionality of whatever it’s linked to.

useImerativeHandle let’s us add some “smarts” to our dumb proxy. Maybe we don’t want to expose all functionality on whatever it’s linked to. In our component example, we might only want to expose the focus function. useImperativeHandle let’s us do exactly that. Before we look at the code, let’s talk a bit more about how this hook builds on what we’ve learned already.
So far, we’ve only used refs to expose DOM functionality, but they’re more flexible than that. When a component forwards a ref to another component, it’s totally blind to what is actually being done to the ref once it’s forwarded. All the forwarding component knows is that at a certain point, the ref will be initialized with some functionality (and if you’re using TypeScript, you know exactly what that functionality is based on how the ref is typed).
When we use useImperativeHandle, instead of attaching the forwarded ref directly to a DOM node, we’re going to explicitly define what functionality it has. Let’s look at how to do that now.
type FormInputRef = { focusInput: () => void };
const FormInputInternal = (
props: Record<string, never>,
ref: React.Ref<FormInputRef>
) => {
const inputRef = useRef<HTMLInputElement | null>(null);
useImperativeHandle(ref, () => {
return {
focusInput: () => inputRef?.current?.focus(),
};
});
return (
<div>
<input ref={inputRef} />
</div>
);
};Let’s walk through the changes step-by-step.
On line 7, we created a new ref called inputRef. This is different than the ref that’s forwarded to the component. We assign this new ref to the ref prop of the input on line 17.
On lines 9-13 we call useImperativeHandle. This hook takes two arguments; a ref, and a function that returns an object defining the behavior that the ref will have. As you can see, the ref we pass to this hook is the ref that’s being forwarded to the component. This ref will be initialized with the functionality we define in the second argument. In this example, we’re specifying that the ref will have access to a single function, focusInput, which calls the focus function on the other ref (the one linked to the input element).
On line 1, we declare a type that defines what functionality the forwarded ref will have. We use that type in the component’s ref argument on line 5. This says that any ref forwarded to the FormInput component will eventually have access to a single function called focusInput that accepts zero arguments and returns no value.
There’s still one more small change to make, and that’s in the useFormInput hook where we’ve encapsulated our business logic.
const useFormInput = () => {
const inputRef = useRef<FormInputRef>(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focusInput();
}
};
return { inputRef, handleFocus };
};On line 2, we updated the type of the ref from HTMLInputElement to FormInputRef and called the focusInput function on it.
Recap
There’s a lot of moving parts here, so let’s step back and review what we’ve done.
- We have a parent component,
App, rendering a child component,FormInput, and we’d like to be able to focus on theFormInput’s internal input element fromApp. - We only want
Appto be able to focus on the input element; we don’t want it be able to do anything else to it. FormInputis wrapped in a call toforwardRefto give it the ability to accept a prop calledref.- In
FormInput, we pass the forwarded ref into theuseImperativeHandlehook, which initializes the ref with the functionality we want it to expose, which is just thefocusfunction. - The
focusfunction declared inuseImperativeHandletakes care of focusing on the input element by using a separate ref that’s declared directly inFormInputand attached to the underlyinginputelement’s DOM node. - The
Appcomponent declares a ref and forwards it toFormInput. Once it’s been initialized, it will be able to call thefocusfunction and only that function.

Another Use Case
We’ve seen how useImperativeHandle gives us the ability to customize the handle exposed by a ref, and how doing this enables us to achieve a higher degree of encapsulation when exposing DOM functionality.
Another use case for useImperativeHandle arises if you need to forward more than one ref to a component.
Note
It's not common that you would need to forward multiple refs to a component, and a need to do so might indicate a design issue that could be remediated through refactoring to acheive greater levels of component composition.
Consider a scenario where we’d like to change the background color of an input’s surrounding div, in addition to focusing on the input, when clicking a button.

Here’s a modified version of our useFormInput hook that handles the added functionality:
const useFormInput = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const divRef = useRef<HTMLDivElement | null>(null);
const handleFocus = () => {
if (inputRef.current && divRef.current) {
inputRef.current.focus();
divRef.current.style.backgroundColor = "peachpuff";
}
};
return { inputRef, divRef, handleFocus };
};A problem arises when we attempt to use the updated hook in our App component, though, because we can only forward a single ref to a component. Fortunately, we can use useImperativeHandle to solve this problem very easily. Here’s an updated version of FormInput:
type FormInputRef = {
focusInput: () => void;
changeBackgroundColor: (color: string) => void;
};
const FormInputInternal = (props: {}, ref: React.Ref<FormInputRef>) => {
const inputRef = useRef<HTMLInputElement | null>(null);
const divRef = useRef<HTMLDivElement | null>(null);
useImperativeHandle(ref, () => {
return {
focusInput: () => inputRef?.current?.focus(),
changeBackgroundColor: (color: string) =>
(divRef.current.style.backgroundColor = color),
};
});
return (
<div ref={divRef}>
<input ref={inputRef} />
</div>
);
};First, we updated the FormInputRef type by adding an additional function, changeBackgroundColor that accepts a string representing the color and returns void. This is effectively updating the contract of the forwarded ref to say that consumers of FormInputRef can now call two functions from the forwarded ref; focusInput and changeBackgroundColor.
Next, we created a new ref called divRef and attached it to the div element.
Finally, we added the changeBackgroundColor function declaration to the useImperativeHandleCall. This function sets the background color of the div to the color function argument.
With our FormInput component updated, all that’s left to do is to update the useFormInput hook.
const useFormInput = () => {
const inputRef = useRef<FormInputRef>(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focusInput();
inputRef.current.changeBackgroundColor("peachpuff");
}
};
return { inputRef, handleFocus };
};Is forwardRef Really Necessary?
At this point, you might be asking yourself why we have to treat ref props differently from other props. And as it turns out, you actually don’t. You can completely avoid using forwardProps to pass a ref as a prop if you just name the prop something other than “ref”.
Below is a slightly modified version of the first example we worked on. It’s identical to the broken version that required using forwardRef, but instead of naming the FormInput ref prop “ref”, we named it “inputRef”. By using a different name for the ref prop, the example now works!
const App = () => {
const { inputRef, handleFocus } = useFormInput();
return (
<div>
<FormInput inputRef={inputRef} />
<button onClick={handleFocus}>Focus</button>
</div>
);
};
const FormInput = ({
inputRef,
}: {
inputRef: React.RefObject<HTMLInputElement>;
}) => {
return (
<div>
<input ref={inputRef} />
</div>
);
};And as you might have guessed, this means that we can indeed pass multiple ref props to a component, eliminating the need to use useImperativeHandle as we did in the previous example.
So is useImperativeHandle worthless, then? Absolutely not. While it might not be necessary in scenarios where we need to pass multiple refs to a component, it is still very useful in its ability to expose limited functionality of a DOM node to a client.
Why does forwardRef even exist?
If it’s so easy to pass a ref to another component without using forwardRef, why do we have that function at all? I admittedly don’t have a great answer to this question, but one reason might be for consistency. In React, native HTML elements use that prop to expose the underlying DOM node, so it makes sense that we should strive for consistency across non-native elements (i.e. custom elements created in your code or third party packages) and universally agree that the ref prop should be reserved for DOM access.
Conclusion & Additional Resources
We’ve covered a lot of ground in this post, and I hope that refs are less mysterious than they might have seemed. Here are some great resources to dive deeper into the topics covered in this post.
Official React Documentation on Refs
Official React Documentation on forwardRef