React useRef() Hook and ref Prop - A Small Guide

Published on: March 8, 2021

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:

  1. Managing focus, text selection, or media playback.
  2. Triggering animations.
  3. 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:

  1. Import useRef from React.
  2. Declare the reference.
  3. Assign the reference to the ref attribute of the element.
  4. Done! Now the .current property 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 useRef in line 1.
  • In line 4, I'm declaring the reference.
  • This reference is assigned to the ref attribute of the element in line 17.
  • The onButtonClick function is an example of how the DOM element can be accessed via the .current property.
  • 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 useRef declaration, I'm assigning an initial value.
  • Then, in the onLoginClick handler (line 6), the mutable object (and its .current property) 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:

  1. When the state of a component is updated via the useState hook, a render will be triggered. On the other hand, when a reference is updated, no render is triggered.
  2. 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! ⚡


ReactHooks