You might have encountered useRef and forwardRef before, but not quite grasped what they are about. In this short article, I remove the mystery and provide some simple, useful examples of what they can be used for.
3 min read
Β·
By Marcus Haaland
Β·
December 24, 2023
Let's start with something you're probably familiar with. Take a look at the search field above. In Vanilla JavaScript, how would you set focus on an element as soon as you enter a website?
A common approach would be to use document.querySelector
:
<!-- input.html -->
<input id="input">
<script>
window.onload = function() {
// π Putting reference to the elemtent in a variable
const inputElement = document.querySelector('#input');
// π Using the reference to set focus
if (inputElement) {
inputElement.focus();
}
};
</script>
But if you use document
directly in React, you bypass the virtual DOM, which can lead to synchronization problems and make the code less predictable.
So, is there something equivalent to document.querySelector
in React?
useRef in React is a hook that can provide the same functionality as querySelector, but in a way that is compatible with React's way of handling the DOM.
Here's how the same behavior is implemented with useRef
:
import React, { useRef, useEffect } from "react";
const InputFocusComponent = () => {
// π ref starts as null, but gets a value as an input element
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
// π ref is coupled to the element
return <input ref={inputRef} />;
}
As you can see from the type, useRef here refers to an input element. But we start the value with null, because it doesn't get a value until the element has been rendered in the document.
The reference to the element provides access to all its values and functions:
... and that includes setting focus:
inputRef.current.focus();
useRef works fine on HTML elements, but when you try to reference components, problems arise:
const InputFocusComponent = () => {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// π ref.current will always be null
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
// π ref on a component doesn't work off the bat
return <CustomInput ref={inputRef} />;
}
The problem is that ref
is a reserved word and will therefore not be available as part of props.
This is warned about in the log:
The log not only gives us a red obstacle but also a solution. When we use ref on an element that is a component, we must enclose the component with the higher-order component forwardRef.
Note also that forwardRef<>
accepts two types of arguments: the type of the element and the typing for the properties (props).
import React, { useRef, useEffect, forwardRef } from "react";
// π Wrapping CustomInput in forwardRef
const CustomInput = forwardRef<
HTMLInputElement, // π Typing the element
React.InputHTMLAttributes<HTMLInputElement> // π Typing the props
>((props, ref) => {
return <input ref={ref} {...props} />;
});
forwardRef
is very useful for being able to control your components, whether it's for your own sake or a library you offer.
There is a special trait with useRef
, which means that it's not only useful for referring to HTML elements. The trait is that useRef
starts its life as an object, {current: null}
. This means that the value we care about, current, can be changed without re-rendering the entire component.
This makes it possible to use useRef
to store values across re-renders. These values do not have to be HTML elements, which means you can use useRef
for a variety of applications. For example, you can refer to time intervals to implement debouncing or count changes across renders.
Here is a simple example of the latter. With each input, a re-render will occur. And the re-render will increment the value for the ref:
function CountEveryRender() {
const [inputValue, setInputValue] = useState<string>("");
const count = useRef<number>(0);
// π Increments every render
count.current = count.current + 1;
return (
<>
<input
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<p>Render Count: {count.current}</p>
</>
);
}
This is what it looks like:
I hope this post helped you understand a bit more about what useRef and forwardRef are trying to solve, and how they work in everyday challenges.
If you want to read more about correct typing of ref, I recommend Matt Pocock's simple approach with ElementRef: https://www.totaltypescript.com/strongly-type-useref-with-elementref