A journey into the world of refs and ref forwarding in React
Nicolas Erny
Posted on May 30, 2022
In many React projects, we need to interact directly with the DOM.
For example, when we want to set the focus to an element. How to do that in React?
Let's start by creating a custom input component.
function MyInput({ ...props }: React.InputHTMLAttributes<HTMLInputElement>) {
return <input {...props} />;
}
Now, we want to get the focus after the component is mounted. To do that, we need to get access to the DOM element.
React refs are a way to get a reference to a DOM node. We use the useRef hook to create a ref object called myInputRef. Then, we add the ref to the input element.
myInputRef enables us to call the focus method on the DOM element. As we want to get the focus after it's mounted, we need to use the useEffect hook. We end up with the following code:
function MyInput({ ...props }: React.InputHTMLAttributes<HTMLInputElement>) {
const myInputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (myInputRef.current) {
myInputRef.current.focus();
}
}, []);
return <input ref={myInputRef} {...props} />;
}
Imagine that our goal is to create a generic custom component. We do not want to get the focus each time we use our React component. The idea is to be more flexible. We would like to expose a ref and let the parent component decide what to do (set the focus or not).
The naive approach is to add a ref property to MyInput.
function MyInput({
ref,
...props
}: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> ) {
return <input ref={ref} {...props} />;
}
Here's the App component that uses MyInput:
function App() {
const inputRef = useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<div>
<MyInput ref={inputRef} />
</div>
);
}
It does not work, and we get the following warning:
Warning: MyInput:
ref
is not a prop. Trying to access it will result inundefined
being returned. If you need to access the same value within the child component, you should pass it as a different prop.
We cannot use a prop called ref, so let's rename it. Here's what the code would be like:
function MyInput({
myRef,
...props
}: React.InputHTMLAttributes<HTMLInputElement> & {
myRef: React.Ref<HTMLInputElement>;
}) {
return <input ref={myRef} {...props} />;
}
function App() {
const inputRef = useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<div>
<MyInput myRef={inputRef} />
</div>
);
}
It works. But according to the React documentation, it's not the recommended solution. A component should use the ref property to be compatible with other components and libraries.
Ref is not available in props, so we must wrap the component in forwardRef. Now, our function component accepts ref as the second argument.
Let's refactor our code to use forwardRef instead. Here's the final version:
type MyInputProps = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>;
const MyInput = React.forwardRef<HTMLInputElement, MyInputProps>(function (
{ ...props },
ref
) {
return <input ref={ref} {...props} />;
});
function App() {
const inputRef = useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<div>
<MyInput ref={inputRef} />
</div>
);
}
This technique for automatically passing a ref through a component to one of its children is called ref forwarding.
I hope that gives you an idea of a good way to use React forwardRef in Typescript.
Posted on May 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.