What is useRef()?
Those with some experience in early versions of React have probably used the ref prop as a way to access elements in the DOM. This was important in a bunch of use cases. For example:
- Managing focus, text selection, or media playback.
- Triggering animations.
- Integrating with third-party DOM libraries.
Newer versions of React bring the useRef() hook, which creates a mutable object that persists its values for the full lifetime of the component—in other words, even between renders of the component.
Let's have a closer look at this hook.
How to use useRef() to access DOM elements?
Quite simple:
- Import
useReffrom React. - Declare the reference.
- Assign the reference to the
refattribute of the element. - Done! Now the
.currentproperty will hold the DOM element and can be accessed in any event of the component lifecycle or method.
In code:
import { useRef, useEffect } from "react";
function MyComponent() {
const elementRef = useRef();
useEffect(() => {
const inputElement = elementRef.current;
}, []);
const onButtonClick = () => {
// "current" points to the mounted text input element
elementRef.current.focus();
};
return (
<>
<input ref={elementRef} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
In the snippet above:
- I'm importing
useRefin line 1. - In line 4, I'm declaring the reference.
- This reference is assigned to the
refattribute of the element in line 17. - The
onButtonClickfunction is an example of how the DOM element can be accessed via the.currentproperty. - Also, in lines 6 and 7, I'm demonstrating how the DOM element can be accessed in any side effect.
Do you need to keep a mutable value around? Use useRef()
The useRef() hook can be used for more than just keeping a reference to DOM elements.
useRef() creates a plain JavaScript object. The only difference between useRef() and creating a { current: ... } JavaScript object is that useRef will provide the same ref object on every render.
So, an initial value can be assigned when useRef is declared, and this value can be read or updated at will since it's mutable.
This is often illustrated by a simple use case: storing the number of clicks on a button.
Here is the snippet:
import { useRef } from "react";
function TrackNumberOfClicks() {
const countRef = useRef(0);
const onLoginClick = () => {
countRef.current++;
console.log(`Login button was clicked ${countRef.current} times`);
};
return <button onClick={onLoginClick}>Login</button>;
}
You will notice that:
- In line 4, along with the
useRefdeclaration, I'm assigning an initial value. - Then, in the
onLoginClickhandler (line 6), the mutable object (and its.currentproperty) can be modified (adding one every time it's called, line 7,countRef.current++). - And it can also be easily read, as shown in line 8 (
countRef.current).
At this point, you might think: I can do the same with useState, so what's the difference?
You're right! In fact, the "counting clicks" use case is often used to explain the useState hook.
There are two important differences:
- When the state of a component is updated via the
useStatehook, a render will be triggered. On the other hand, when a reference is updated, no render is triggered. - Updating the state is an asynchronous call (the state is updated after re-render). On the other hand, a reference is updated synchronously—in other words, the value is available right away.
Happy coding! ⚡