How to use React's useRef() hook

At first sight, it may not be clear what purpose the useRef hook fulfills. If you know how to use it, however, it can be a useful tool in certain situations.

In this article, I'll explain everything you need to know about it, including how, when, and when not to use it.

What is React's useRef hook?

React's useRef hook, short for reference, allows us to persist data across renders without causing the component to rerender.

A typical use case for this hook would be to store a DOM element, which we can use to access it programmatically. You can also use it to keep a reference to the previous state of a component.

How to use useRef

The first step of working with refs is the initialization.

We do this by calling React's useRef function, passing as the only argument the initial value we want our reference to have.

const reference = useRef('initial value');

The function returns an object of the following shape:

{
  current: 'initial value',
}

Any change that we make to the reference object will persist across all renders of our React component.

You could say that there's already useState for this, and in 90% of cases, you're right. But what happens if you want to store the state without rerendering the component?

Let's say you want to display the number of renders in the UI. You can't just store this information in the state and update the state on every render, because calling setState will cause another render, and thus we'd end up with an infinite loop.

Instead, we can store this information in a reference, which persists between multiple renders but doesn't update the state.

const App = () => {  
  const updates = React.useRef(0);  const [text, setText] = React.useState('');

  React.useEffect(() => {
    updates.current++;  });

  return (
    <div className="app">
      <div className="blue-wrapper">
        <input value={text} placeholder="Write something" onChange={(e) => setText(e.target.value)} />
        <Updates updates={updates.current} />        <Tile />
        <TileMemo />
      </div>
    </div>
  );
};

Part of the code I wrote to illustrate React.memo(). Check out the code on Codepen.

In this example, I increment the update count on every render inside the useEffect hook. The Updates component then displays this information.

Using refs in React

Probably, you won't write a lot of code that keeps track of the number of rerenders of a component.

A more common scenario is that you want to focus an input field when the user clicks a button.

Unfortunately, React doesn't have a built-in way of doing this, so we'll need to access the DOM element somehow and call the focus function that's part of it.

If we did this in pure JavaScript, the code for this would look similar to the following:

const inputElement = document.getElementById('input-element-id');
inputElement.focus();

In React, we could do it like this too, but React provides a solution that's much easier to use:

Every element in JSX has an optional ref property. If we pass a ref object to it (which we created with useRef), it gets updated to contain the HTML element.

We can then use this reference to manipulate the DOM as we want:

const App = () => {
  const inputRef = useRef(null);
  return (
    <>
      <input
        ref={inputRef}        value={name}
        onChange={handleChange}
      />
      <button
        onClick={() => inputRef.current.focus()}      >
        Focus
      </button>
    </>
  );
};

Check out the full example on codepen.

If we log the inputRef object, we'll see that it contains the DOM element:

{ current: input }

Storing the previous state in a ref

The last use case for useRef that I want to show you is holding a reference to the previous state.

In my example on codepen, I've created a component that updates the name variable with an input field.

To hold a reference to the last value of name, we can simply use the useEffect hook and make it execute on every change. In the callback, we then update the reference.

const prevNameRef = React.useRef('');

React.useEffect(() => {
  prevNameRef.current = name;
}, [name]);

The full example looks like this:

const App = () => {
  const [name, setName] = React.useState('...');
  const inputRef = React.useRef(null);
  const prevNameRef = React.useRef('');
  React.useEffect(() => {    prevNameRef.current = name;  }, [name]);
  const handleChange = (e) => {
    setName(e.target.value);
  }

  return (
    <>
      <h1>My name is {name} and was {prevNameRef.current}</h1>      <input
        ref={inputRef}
        value={name}
        onChange={handleChange}
      />
      <button
        onClick={() => inputRef.current.focus()}
      >
        Focus
      </button>
    </>
  );
}

When not to use useRef

To developers that are new to React, it might be tempting to use this hook to access any DOM element and use JavaScript to get your code to work.

However, during my time working as a React developer, I only needed to use references a couple of times. By default, we should try to solve the problem using React's state and props, which will make our codebase much easier to maintain.

React's state and prop system is the best and most reliable way to communicate between components. As developers, we're best served using this and only falling back to native JavaScript for special use cases like the ones I described above.

Conclusion

React's useRef hook is a great tool to persist data between renders without causing a rerender and to manipulate the DOM directly.

It should only be used sparingly in situations where React doesn't provide a better alternative.

If you have any open questions, please don't hesitate to leave a comment, so I can make this article even better with your help.

If you liked this article, you might also want to check out my other articles about React:

  • How to use React's useEffect() hook

    Learn everything you need to know about this hook to get the most out of your functional components.

  • When does React re-render components?

    This article does a deep dive into how React's rendering mechanism works. If you want to learn more about the VDOM and how to optimize render performance, I recommend that you check it out.