When to Use the React useRef() Hook?

While it is possible to use the React useRef() hook to store any value, you should only use it for storing a value that:

  1. Needs to be persisted between component re-renders;
  2. Does not need to be tracked by React for changes, and;
  3. Does not impact the component's rendering logic or need to be displayed on the screen.

This is because when you change the "ref.current" property, React does not re-render your component, and is not aware of when you change it.

For this reason, useRef() is not a good candidate for managing state, or for storing information you want to display on the screen. There are other hooks that are better suited for this purpose, such as useState or useReducer. These allow you to manage state in a declarative way, where you define what should happen when a state changes, and React takes care of rendering updates.

If you use useRef() to store state values that need to trigger a re-render, it can lead to unexpected behavior. In some cases, it may work, but it is not considered a best practice.

As noted in the official React docs, typically, you will use a ref when your component needs to "step outside" React and communicate with external systems or browser APIs, such as in the following situations:

  • Storing timeout or interval IDs;
  • Storing and manipulating DOM elements;
  • Storing objects that are not directly involved in rendering the component's output;
  • Storing some value, that doesn't impact the rendering logic.

That being said, it's important to avoid overusing the useRef() hook because it does not follow the usual React data flow, which relies on state and props to trigger component re-renders. The React documentation suggests treating refs as a last resort solution, reserved for cases such as the ones mentioned above.

To demonstrate how useRef() works, consider for example, the following code, where an alert is shown with a counter value that's updated each time the button is clicked:

import { useRef } from 'react';

function Counter() {
  const ref = useRef(0);

  function handleClick() {
    ref.current += 1;
    alert(`Clicked ${ref.current} times!`);
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

export default Counter;

Compare this to the following, where "ref.current" is used directly in the rendering logic of the component:

import { useRef } from 'react';

function Counter() {
  const ref = useRef(0);

  function handleClick() {
    ref.current += 1;
    alert(`Clicked ${ref.current} times!`);
  }

  return (
    <button onClick={handleClick}>
      Clicked {ref.current} times!
    </button>
  );
}

export default Counter;

When you run this code, and click on the button, you will see that the counter value in the alert is correctly updated but the value displayed on the button is not. This happens because when ref.current is assigned a new value, it does not trigger a re-render.


This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.