useRef Hook in React - Use Cases, Code Examples, Interview Prep
The best guide on the useRef hook you'll find on the internet. Learn about its use cases with code examples, theory deep dives, caveats, and interview prep
Kaushal Joshi
Dec 13, 2024 • 12 min read
The useRef
hook provided by React.js is often a neglected React hook for interview preparation. Yet, interviewers focus on it as much as they do on other core React concepts. The useRef can be challenging to understand because it behaves differently from other hooks. It bypasses some of React's core rules, making it more confusing to understand.
In this article, we will dive deep into React’s useRef hook. We will start from the basics and understand how it works, how to use it, and how to avoid common pitfalls. Then we’ll see how useRef differs from useState
and createRef
. Finally, we’ll end with useRef interview questions to revise our learnings.
Prerequisites
This article presumes that you have a basic understanding of React.js. This includes states, JSX, component-driven architecture, functional components, etc. If you don’t, I’d recommend learning React first. react.dev/learn is a good place to start your React journey.
What is useRef?
useRef is a built-in React hook that is used to store values across renders. The value stored with the useRef hook persists even if a component rerenders. Similarly, if the value inside the useRef object gets updated, the component does not re-render.
Here’s the basic syntax of useRef hook:
const ref = React.useRef(initialValue);
Parameters of useRef hook
initialValue
: This is the initial value you want to have for the ref object'scurrent
property. It is ignored after the initial render when thecurrent
property is updated for the first time.
Return values of useRef hook
ref
: useRef returns an object with a single property:current
. During the initial render, it is set toinitialValue
. You can later set it to something else. On future renders, it returns the same object as the previous render.
Use cases for useRef hook
If you have never used this hook before, I know you are confused. Let’s understand why would you want to store a value across renders, or update it without triggering a component re-render.
Referencing a value with useRef
The most basic use case of the useRef hook is to reference a value across rerenders. Let me explain.
Ideally, we use React’s useState
hook to store values. But updating a state causes React to re-render. Sometimes, we don’t want that. Consider scenarios like tracking data that won’t be shown on the UI — like timers, counters, previous states, etc. We want to update them without triggering the rerender.
That’s when we prefer useRef over other hooks. It is used when we want to do the following:
Update a value without causing a component to rerender.
Access a particular value even if a component rerenders.
Here’s a simple example of the useRef hook:
import { useRef, useEffect } from "react";
export default function Counter() {
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
console.log(`Component has rendered ${renderCount.current} times.`);
});
return <div>Check the console for render count.</div>;
}
In this example, renderCount
will increase with each render, but changing its value doesn’t cause a rerender. Similarly, the data persists even if the component rerenders.
Manipulating the DOM with useRef
Another common use case is to access DOM directly. Traditionally, React discourages accessing DOM directly from within the component. But sometimes you need to access DOM directly anyway. Consider scenarios like:
Managing focus and selection of HTML elements
Measuring or Modifying Element Dimensions
Controlling Animations or Transitions
Interacting with Third-Party Libraries
You can pass the return value of the useRef hook to the ref
attribute of the HTML node you want to manipulate.
import { useRef } from "react";
export default function InputFocus() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Click button to focus" />
<button onClick={focusInput}>Focus the input</button>
</div>
);
}
Here, inputRef
is used to directly access the <input>
DOM element and focus on it when the button is clicked.
Handling Intervals and Timeouts
Another common use case for the useRef hook is to handle intervals or timeouts. Since useRef
can persist a value across renders, it’s perfect for holding references to timers without causing issues with rerenders.
import { useRef, useEffect, useState } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = window.setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, []);
return <div>Seconds passed: {seconds}</div>;
}
In this example, timerRef
holds the interval ID and persists across renders. When the component unmounts, we can use timerRef.current
to clear the interval.
Avoiding Common Pitfalls and Errors While Using useRef hook
Now let’s look at some common errors you might encounter while using the useRef hook.
Cannot read properties of null (reading 'useRef')
This error typically occurs when you're trying to interact with a ref
before it is properly initialized.
To solve this, always ensure the ref
is properly assigned before accessing it. For instance, in the input focus example, inputRef.current
could be null
initially, so you must ensure it's mounted before calling methods on it.
if (inputRef.current) {
inputRef.current.focus();
}
useRef not updating and rendering new values
If you’re reading this article carefully, you must have understood why this happens and how you could fix it.
Let’s look at an example.
import { useRef } from "react";
function RefExample() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log("Ref value:", countRef.current); // Logs the updated value
};
return (
<>
<p>Current ref value: {countRef.current}</p> {/* This will NOT update */}
<button onClick={handleClick}>Increment Ref</button>
</>
);
}
In the code above, the UI will NOT update when you click the button, even though countRef.current
is changing. The useRef
does not update the UI when the value current
is changed because it doesn’t cause rerender.
If you need the UI to reflect changes, you should use useState
instead of useRef
. Here’s how the same example would look with useState
:
import { useState } from "react";
function StateExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // This will trigger a re-render
};
return (
<>
<p>Current count: {count}</p> {/* The UI will update */}
<button onClick={handleClick}>Increment State</button>
</>
);
}
Always remember,
useRef
is designed to store values without causing rerenders. For any reactive changes to the UI, you should useuseState
.
useRef vs useState hook — Which One to Use?
React also provides another hook to store data, called useRef
hook. We just saw an example in the previous section. You must be thinking why use a confusing hook like useRef when useState exists? Let’s find it out.
The useState
hook is used when we must update the content on the screen if the state’s value changes. By default, React rerenders a component, along with its child component if the state updates. Similarly, when a component rerenders, the state is also updated with the latest value.
The useRef
hook, on the other hand, does not trigger a rerender. Hence we can persist a value even if a component rerenders, or update a value without causing a component to rerender.
useRef vs createRef — What’s the Difference?
If you have been using React for quite a long, or have referred to an older video or documentation, you must have come across createRef
. The main difference between useRef
and createRef
is that the first is used in functional components whereas the latter is used in class components.
The key difference is that createRef
always creates a new reference every render, while useRef
maintains the same reference across renders.
Here’s the basic syntax of createRef()
:
class MyComponent extends React.Component {
inputRef = React.createRef();
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return <input ref={this.inputRef} />;
}
}
If you are working with functional components, you should always prefer useRef
and avoid createRef
.
The official documentation for
createRef
TypeScript with useRef Hook
When using useRef
in TypeScript, it's important to define the correct types for the values you're storing, especially if you're referencing DOM elements.
Here's how you can use useRef
with TypeScript to reference DOM elements:
import { useRef, useEffect } from "react";
function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null); // HTMLInputElement type
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus(); // Focus the input when the component mounts
}
}, []);
return <input ref={inputRef} type="text" placeholder="Focus on me!" />;
}
Here, useRef<HTMLInputElement>(null)
creates a ref for an <input>
element. The null
is the initial value because the DOM element is not available until after the component has been rendered. You can get the type of a JSX node by simply hovering over it, over searching it on Google or ChatGPT :P
Why Do We Need to Store Values Across Renders?
Let’s say you have a timer that you want to increment every second. If you store the timer in a regular variable, the timer will be reset each time the component re-renders.
let timer = 0;
useEffect(() => {
const interval = setInterval(() => {
timer += 1;
console.log(timer); // Timer resets on every re-render
}, 1000);
return () => clearInterval(interval);
}, []);
In React, every time a component rerenders, it runs the entire function again. During each render, all variables and values inside the function are re-initialized. This means that any value stored in regular variables will be reset after every render.
To persist values between renders we need a way to store them that won’t be reset when the component rerenders.
const timerRef = useRef(0); // Use useRef to persist the timer value across renders
useEffect(() => {
const interval = setInterval(() => {
timerRef.current += 1; // Update the ref value
console.log(timerRef.current); // Logs the updated timer value
}, 1000);
return () => clearInterval(interval); // Clean up the interval on unmount
}, []);
This is the correct version of the previous code. It persists the interval even if the component rerenders.
Why Can't We Use the document
Object to Access the DOM Directly?
While you can technically use the document
object to access and manipulate the DOM directly, it’s generally discouraged in React for several reasons:
React’s Virtual DOM: React uses a virtual DOM to manage changes to the real DOM efficiently. When you directly manipulate the real DOM with
document.getElementById
ordocument.querySelector
, you’re bypassing React’s virtual DOM system. This can lead to inconsistencies between what React thinks is rendered and what’s actually in the DOM, causing bugs and unexpected behavior.Component Reusability and Isolation: React encourages a component-based architecture. By directly manipulating the DOM outside of React, you break the isolation of your components. This reduces maintainability and testability, as components are no longer self-contained.
Re-renders: React can re-render components at any time based on state or props changes. If you manipulate the DOM directly, your changes can be overwritten by React when it re-renders a component. Using refs ensures that React’s re-renders do not interfere with your DOM manipulations.
Instead of using document
, React recommends using useRef
to interact with DOM elements while still adhering to React’s virtual DOM principles.
Why Does useRef
Have a current
Property, and Why Can't We Store Data Directly?
The useRef
hook returns an object with a current
property because it needs to store mutable values in a way that React can manage efficiently, without causing re-renders.
Why the current
Property?
Consistency: The
current
property is always there. It provides a consistent interface, whether you are storing a DOM node reference or any mutable value. This makesuseRef
more predictable, regardless of what kind of value you're holding in it.Rerender Management: The value stored in
ref.current
is outside of React’s state management and re-render cycle. If you were to store the value directly in theref
object itself (rather thanref.current
), there would be no way for React to isolate the value across re-renders. Usingref.current
allows React to maintain this separation and optimize performance.
Why Can’t We Store Data Directly?
Mutable Object:
useRef
is designed to hold mutable values, and this mutability needs to be managed carefully. If React allowed data to be stored directly in the object, the reference could be reassigned completely, potentially causing bugs or confusion. Thecurrent
property provides a controlled way to manage this mutability without accidentally reassigning the reference itself.Uniform Interface: Whether you're referencing a DOM node or storing a custom value, the uniformity of using
ref.current
makesuseRef
versatile and avoids confusion. The reference (ref
) itself should not change between renders, but itscurrent
value can.
In short, the current
property ensures that you always access or modify the intended value, and React maintains control over the reference lifecycle.
Recap — useRef hook, In a Nutshell
Let’s take a deep breath and recap everything we learned today.
The useRef
hook in React allows you to store persistent values across component renders. These values could be valid JavaScript primitive or non-primitive types or HTML DOM nodes. The hook takes an initial value as a parameter and returns an object with a single property called current
, which can be updated without triggering a rerender of the component.
You must use the useRef hook if you want to —
Update a value without causing a component to rerender
Access a particular value even if a component rerenders
Common use-cases:
Referencing a value with
useRef
Manipulating the DOM with useRef
Handling Intervals and Timeouts
Interview Preparation
If you're preparing for frontend interviews, the following list of questions would help you prepare for the useRef hook in depth:
What are the common pitfalls you might encounter while using the
useRef
hook?What’s the difference between the
useState
and theuseRef
hook? When should you use which?What’s
createRef
, and how does it defer fromuseRef
?Why does useRef have a
current
property, and why can't we store data directly?Why can't we use the
document
object to access the DOM directly?
Wrapping Up
Thank you for sticking through until the end! I hope you found this article helpful and learned new concepts about the useRef hook. If you did, you must share this article with your friends and peers! After all, helping your peers to grow is such a wonderful feeling.
On a side note, as you read this blog, I can’t help but think you’re preparing for job interviews! If that’s the case, don’t forget to check out Peerlist! Whether you’re searching for jobs, looking for a way to share your side projects, or building a credible proof of work, Peerlist is the go-to stop for it!
Until then, happy coding! May your skills persist across different domains 🪄