Demystifying React Hooks — useRef

In this article, we will discuss some common use cases for React’s useRef Hook.

austin
7 min readDec 12, 2022

Getting Started

We will use the Profiler from the React Dev Tools to see how our components are rendering. If you don’t have the React Dev Tools and plan to follow along, you’ll need to pause and download it now.

If you’d like to follow along in your local IDE, you can find the GitHub Repo here.

  • fork and clone
  • cd client
  • npm i
  • npm start

Starter Code

As always, we’ll start with a tour of our codebase. This time, App.js is a simulated Login Form.

We have initialFormValues, an object with a blank email and password, which we use to initialize our formValues state.

formValues initialization

We then have boilerplate handleChange and handleSubmit functions, and the handleSubmit logs formValues to the console.

handleChange and handleSubmit

Finally, our JSX renders the form and assigns the appropriate onChange and onSubmit attributes.

our return statement

What’s the Problem?

So you may be asking, “What’s wrong with this component? It works as expected, and I’ve written forms like this countless times.”

So have I, friend. So have I.

Open your React Dev Tools and click the Settings cog. Next, click the ‘Profiler’ tab and tick on the option, “Record why each component rendered while profiling.”

setup profiler to record why each render occurs

Now open the Profiler tab, and you will see a blue dot in the upper left corner. Click it to start profiling. Next, type in the inputs and click the (now red) dot to stop profiling.

begin profiling

Click the App component on the left inside the Profiler tab, and you'll see a list of all the renders. Notice the Profiler provides the same reason for each:

Why did this render?

  • Hook 1 changed

The component re-rendered on every keystroke.

Profiler telling us the reason for each render

Minimizing Renders

If our form’s sole purpose is to submit the inputs elsewhere, then we don’t need the component to re-render in real time. We don’t care what the value is while the user is typing. We only care about the value when the form is submitted.

This may seem harmless in a small application, but it can significantly impact our application as it scales. And part of being a good React developer is being mindful of the performance of our applications by minimizing renders.

Enter useRef

In the broadest sense, the useRef Hook creates a mutable variable that persists between renders. It provides the ability to store a value we can access outside the render cycle.

Let’s try using useRef to store our render count.

We’ll start by importing useRef in our current import.

importing useRef

We will initialize renders with useRef and set it to 0. When we initialize a ref, the Hook creates an object with a current property. current is the property we must use to update the ref's value.

initializing rendersRef

We'll use a useEffect Hook without the dependency array to increment renders every time the component renders. And take note that we change the value of a ref, unlike useState, we can do so directly.

importing useEffect and incrementing rendersRef

Let’s render renders in our JSX.

displaying rendersRef.current in our JSX

When we type in the input, we see our render count incrementing in real-time. Neat, I guess, but not particularly useful.

render count increments as we type

From the React Docs:

useRef returns a ref object with a single current property initially set to the initial value you provided. On the next renders, useRef will return the same object. You can change its current property to store information and read it later. This might remind you of state, but there is an important difference.

Changing a ref does not trigger a re-render. This means refs are perfect for storing information that doesn’t affect the visual output of your component.

…information that doesn’t affect the visual output of your component.

Kind of like a login form?

Refactoring Our Form with useRef

Let’s start by creating two new refs to store our email and password values and initialize them as null.

initializing emailRef and passwordRef

When using the useRef Hook to reference a DOM element, associating it is incredibly simple. All we need to do is add a ref attribute to the element and provide it our ref variable as its value.

assigning the refs to their JSX elements

Next, we need to refactor our handleSubmit function. Instead of logging formValues, we'll create an object, setting the values as each ref's current property.

refactored handleSubmit

Type in the inputs and click Login. You should see your object logged properly while the render count remains 0. The Profiler also finds no activity. Changing the value of our refs did not cause the component to re-render.

useRef is not causing our component to re-render

With that working, we no longer need the following:

  • email onChange attribute
  • password onChange attribute
  • handleChange function
  • formValues state
  • initialFormValues object
  • useState import

Though we aren’t directly using it anymore, we should keep the name attributes for accessibility.

Test the application again. We removed a lot of code, but it still works as expected!

useRef vs. useState

This accurately demonstrates the point raised in the React Docs: updating useRef does not trigger a re-render, which is probably the most significant difference between useRef and useState.

In addition, you’ll notice that when we updated the current property on our ref, we did so directly. Never do this with useState. Instead, you must always use the setter function if you wish your UI to react to the change. You can read about this behavior here.

Yes, I know. We literally just used useRef to affect a change in the DOM. In my defense, try to use useState to track your render count and include itself in that count. If you can figure out a way without causing an infinite re-render, please reach out. I'd like to know.

One Last Use Case

Before concluding this article, I’d like to address what I think is the simplest common use case for useRef. Let’s start by moving our render count below the section tag containing our form. We’ll put it in its own section tag.

moving renders to its own section tag

Next, we will create a formRef for our form and assign it accordingly. Let’s add a button with the text, Scroll to Render Count.

initializing forRef, assigning it, adding a button

Then we will initialize renderCountSectionRef, assign it to our render count container, and add a button with the text Scroll to Form.

Let’s create a new function called scrollToElement that expects a ref as a parameter and scrolls us to said ref.

srollToElement function

Now, we will set the onClick property to scrollToElement with the appropriate ref as its argument.

Please note that using inline functions will make for a less performant app, but that refactor would require more custom logic and is outside the scope of this article.

setting button onClick to scrollToElement

Now, when we click the buttons, we scroll about the page! This feels a lot like a Scroll To Top or Jump to Recipe button, doesn't it?

scrollTo buttons working

Since scrolling to a specific element doesn’t demand a re-render, it is a much better use case for useRef than useState.

A Caution

After realizing that we can affect changes on DOM Elements directly with useRef, it may be tempting to use this method as an analog to querySelector or getElementBy—.

This is an anti-pattern and should be avoided if possible. It can cause your UI to fall out of sync with your state and, in a larger application, can have an unforeseen ripple effect. These bugs will be challenging to track down.

Conclusions

In this article, we discussed the functionality of useRef, how it persists through renders, and how, unlike useState, it does not cause re-renders. We also explored one of the most common uses of useRef: to navigate our user to different DOM elements.

I’m always looking for new friends and colleagues. If you found this article helpful and would like to connect, you can find me at any of my homes on the web.

GitHub | Twitter | LinkedIn | Website

Resources

--

--