useState vs useRef: How to Choose the Right React Hook
State management is a critical part of most front-end applications. In React, you have a few mechanisms to track state, but two common ones are the useState
and useRef
hooks.
If you're not familiar with these hooks, it can be confusing to know when to use each one. In this post, we'll compare the useState
and useRef
hooks and build some basic intuition around when to choose each one.
The useState hook lets you add state to functional components.
The useState
hook is the basic building block to manage state for functional components. The hook returns a tuple where the first element is the state and the second element is a function to update that state.
The key part of the updater function is that it's designed to trigger a re-render. When a React component re-renders, the JSX is recalculated and the underlying HTML is updated to reflect any state changes.
As you click the button in the example below, you'll notice that:
The count variable is incremented
The component re-renders
The HTML changes
Great!
We can manage state in React now, but how does useRef
come into play?
The useRef hook lets you track state that isn't needed for rendering.
Just like the useState
hook, the useRef
hook lets you track state. The hook returns an object with a current
property that contains the state. To update the state, you just need to update the current
property.
The key difference is that any update to this state won't trigger a re-render.
Here's a similar example as above, but with the useRef
hook instead. There's also a button to force the component to re-render.
When you click the count button, notice how the state doesn't update. This is because updates to useRef
values don't trigger re-renders.
That said, the underlying state is still updating.
But how do we prove that?
Well, if we use the other button to force a re-render, you'll notice that the button's value changes based on the current state tracked by the useRef
hook:
Note: Because updates to useRef values don't trigger re-renders, you want to make sure you don't use a ref's value in your JSX. I know I'm breaking that rule above, but the example is just for demonstrative purposes.
Ok! So we want to use a ref when we track state that doesn't trigger a re-render. But what kind of state shouldn't trigger a re-render?
Let's take a look at a couple of examples.
How to scroll an element into view.
It's common to track DOM elements with the useRef
hook. If you pass your ref variable to an element's ref
prop, React will automatically set your variable's current
property to the DOM element.
Once you have access to the DOM element, you can do various things like reset a form input or scroll an element into view.
In the example below, we have a dummy recipe blog post. Like most recipe blog posts these days, there's content before the recipe that provides some background about the dish.
Wouldn't it be nice if there were a button that let you skip all that extra information and jump straight to the good stuff?
Let's see how we might be able to do that with the useRef
hook.
Note: I used ChatGPT to generate a dummy recipe blog post, so apologies if there are mistakes in the content.
Notice how we're passing the recipeRef
to the h2
element Creamy Spinach and Mushroom Alfredo Pasta
. React automatically sets the current
value of our ref to the h2
DOM element. With that in place, we can use the ref to scroll the element into view when the button is clicked.
Easy!
How to build a countdown timer.
Another good use case for the useRef
hook is to keep track of an interval ID that's returned from a setInterval call.
Let's say we had a countdown timer that shows the countdown in seconds. The easiest way to build this would be to use the useState
hook to show the current timer value and then update the state every second.
That part is easy.
But what if we wanted a way to start and stop the timer? This is a little bit more tricky. The good news is that we can use the useRef
hook for this. We can keep track of an interval ID when the timer starts and use it to clear the interval when the timer stops.
Let's see what that looks like:
Note: When we update the count, we're using a functional update because the function we pass to setInterval will have a stale version of the count variable due to its closure.
When to use each hook.
Hopefully, the examples above will help you build intuition around when to use each hook. But as a general rule of thumb use the useState
hook when you want your component to re-render and the useRef
hook when you don't want your component to re-render.
Subscribe to my newsletter
Read articles from Sai Hari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sai Hari
Sai Hari
Content about improving developer process, tooling and code. Level 4 Vim Sommelier | I think, therefore I program