Easily restore your project to a previous version with our new Instant One-click Backup Recovery

React useRef() - A complete guide

Learn how to use React's useRef Hook for DOM manipulation, performance optimization, and more in this in-depth guide.
Motunrayo Moronfolu

Written by Motunrayo

Dec 09, 2024
React useRef() - A complete guide

The useRef Hook is a versatile tool, often overlooked but crucial for managing references, optimizing performance, and interacting directly with the Document Object Model (DOM). While it might not be as widely discussed as useState or useEffect, it plays a pivotal role in many React applications.

This guide will discuss the useRef Hook, its use cases, practical applications, and drawbacks. By the end, you'll be well equipped to leverage its full potential, unlocking new levels of efficiency and control in your React projects.

#What is useRef()?

What is useRef()?

The useRef() Hook is a built-in React feature that persists values between component re-renders. Unlike state variables managed by useState, values stored in a ref object remain unchanged across renders, making it ideal for scenarios where data doesn't directly affect the UI but is essential for the component's behavior.

#How does React useRef() work?

When React encounters a useRef() Hook, it returns a plain JavaScript object with a single property: current. This current property stores mutable values, which can be of any type, from simple values like numbers and strings to complex objects, functions, or even references to DOM elements.

How does React useRef() work?

React assigns the initial value you define to the current property of the returned reference. React will set the value of the useRef to undefined if you don't provide an initial value. Importantly, you can update this current value directly without triggering a re-render of the component. This can be seen in the snippet below:

import { useRef } from "react";
function MyComponent() {
const reference = useRef(true);
console.log(reference.current); // true
}

Also, the returned reference object is mutable. You can update the current value directly, as shown in the snippet below.

import { useRef } from "react";
function MyComponent() {
const reference = useRef(true);
const handleUpdate = () => {
reference.current = !reference.current;
};
console.log(reference.current); // true
return <button onClick={handleUpdate}>Update</button>;
}

Clicking the “Update” button changes the reference.current value from true to false and the other way around.

#Use cases of useRef() React Hook

Beyond its ability to persist values, the useRef() Hook has several vital roles in React applications:

Accessing DOM elements

Imagine a login page where users need to enter their username and password. To enhance the user experience, you can automatically direct their focus to the username field as soon as the page loads.

To achieve this, use the useRef 's capacity to access rendered DOM elements. This feature returns the referenced DOM element and its properties, providing room for direct manipulations.

import { useRef, useEffect } from “react”
function Login() {
const usernameRef = useRef(null)
useEffect(() => {
usernameRef.current.focus()
}, [])
return (
<>
<form>
<input type="text" ref={usernameRef} />
</form>
</>
)
}

The above snippet shows a Login component that renders a form with an input field for the user’s username. Also, it defines a username reference with the useRef Hook, which the focus() method is called on. This is executed inside a useEffect Hook, so it runs immediately after the UI finishes loading.

Tracking previous values

By storing the previous value of the input element as a state variable in a ref, you can monitor changes and address them accordingly. This technique is useful for implementing "undo" functionality, creating custom Hooks that need to compare previous and current values, or optimizing performance by preventing unnecessary calculations.

import { useState, useRef, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const previousCountRef = useRef(count); // Store previous count in a ref
useEffect(() => {
if (previousCountRef.current !== count) {
console.log('Count changed:', count, '(Previous:', previousCountRef.current, ')');
}
previousCountRef.current = count; // Update the ref
}, [count]); // Run the effect only when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In this example, the useRef Hook stores the previous value of the count state variable. Every time the count changes, the useEffect Hook compares the new count to the value stored in the previousCountRef and logs a message to the console if they differ. This way, you can track how the count value evolves over time.

Managing timers and intervals

The useRef simplifies the management of timers and intervals within your components. You can store the timer's ID in a ref, allowing you to start, stop, or reset it as needed without triggering unnecessary re-renders.

import { useState, useRef, useEffect } from 'react';
function myTimer() {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef(null); // Ref to store the timer ID
const startTimer = () => {
timerRef.current = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000); // Update every second
};
const stopTimer = () => {
clearInterval(timerRef.current);
};
const resetTimer = () => {
clearInterval(timerRef.current);
setSeconds(0);
};
// Cleanup function to clear the interval when the component unmounts
useEffect(() => {
return () => clearInterval(timerRef.current);
}, []);
return (
<div>
<p>Time elapsed: {seconds} seconds</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
<button onClick={resetTimer}>Reset</button>
</div>
);
}

In this example, a timer component was created using the useState and useRef Hooks. The useState Hook manages the seconds variable, which keeps track of the elapsed time. Meanwhile, useRef stores a reference to the interval ID (timerRef), which is responsible for updating the seconds every 1000 milliseconds (1 second).

The functions startTimer, stopTimer, and resetTimer control the timer's behavior. startTimer starts the interval, stopTimer stops it, and resetTimer resets the elapsed time to zero.

Building custom Hooks

When creating custom Hooks that need to maintain an internal state between renders, useRef becomes an invaluable asset. It enables you to store and update data privately within the Hook, promoting reusability and encapsulation of complex logic.

Imagine having multiple components that are required to maintain their previous state. It will appear too tedious to write this logic individually for every component. To avoid this, create a custom Hook using the useRef Hook as follows:

First, create a Hooks folder and a file usePreviousState.js, then add the following snippet:

import { useRef, useEffect } from "react";
export default function usePreviousState(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}

In the snippet above, a useRef instance was created, then on every page re-render, assign the value passed to the usePreviousState Hook to the ref.current. Finally, return the value of the ref.current.

To illustrate the custom Hook's utility, let’s revisit the 'tracking previous values' scenario

import { useState, usePreviousState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const previousCount = usePreviousState(count); // Store previous count in a custom Hook
useEffect(() => {
if (previousCount !== count) {
console.log('Count changed:', count, '(Previous:', previousCount, ')');
}
previousCount = count; // Update the previous state
}, [count]); // Run the effect only when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In the snippet above, all instances of the initial useRef were replaced with the newly created custom Hook. This shows that creating a custom Hook makes it possible to reuse complex logic.

#Optimizing performance

Beyond its DOM manipulation capabilities, useRef shines as a performance enhancer. Imagine you have a computationally intensive task within your component, such as filtering a dataset or performing calculations, re-executing these operations on every render would be wasteful.

Storing values of these calculations within a ref variable using useRef can avoid unnecessary re-computations. Since refs persist across renders without triggering updates, the calculated value remains accessible and doesn't need to be recalculated unless the underlying data changes. Let's look at the example shown below:

import { useState, useRef, useMemo } from "react";
export default function calculationComponent() {
const [inputNumber, setInputNumber] = useState(10); // Initial Calculation number
const calculateResult = useRef(null); // Ref to store the calculated result
const calculateFib = (n) => {
if (n <= 1) return n;
return calculateFib(n - 1) + calculateFib(n - 2);
};
// Memoize the calculation using useMemo
const memoizedCal = useMemo(() => {
if (
calculateResult.current &&
calculateResult.current.input === inputNumber
) {
// If the input hasn't changed, reuse the cached result
return calculateResult.current.result;
} else {
// Calculate the result and store it in the ref
const result = calculateFib(inputNumber);
calculateResult.current = { input: inputNumber, result };
return result;
}
}, [inputNumber]);
return (
<div>
<input
type="number"
value={inputNumber}
onChange={(e) => setInputNumber(parseInt(e.target.value, 10))}
/>
<p>
Result of {inputNumber} is: {memoizedCal}
</p>
</div>
);
}

In the snippet above, the useRef Hook, named calculateResult, is employed to optimize performance by caching the results of a computationally expensive calculation like the Fibonacci sequence. The calculateResult.current property stores both the input number and the calculated result, allowing for comparison on subsequent renders.

The useMemo Hook is used to memoize the calculation. It checks if the input number (inputNumber) has changed since the last render. If not, it returns the cached result from calculateResult.current, avoiding redundant computation. If the input number changes, the Fibonacci calculation is performed, and the result is stored in calculateResult.current for future reuse.

This memoization strategy improves the component's performance by preventing recalculations whenever the component re-renders due to unrelated state changes. While useRef offers performance benefits, knowing its potential drawbacks is essential.

#Drawbacks of useRef Hook

  • Manual management: Unlike state variables managed with useState or useReducer, changes to the .current property of a ref attribute do not automatically trigger re-renders of your component. This means you need to manage updates and trigger re-renders manually when necessary and can add extra complexity to your code.
  • Potential memory leaks: If you're not careful, you can create dangling refs. This happens when a ref is still holding onto a reference to a DOM element or other object that has already been unmounted or removed from the DOM, leading to memory leaks.
  • Overuse: While refs are helpful in certain scenarios, overusing them can lead to a more complex and less maintainable codebase.
  • Debugging challenges: Since changes to refs don't trigger re-renders, debugging issues related to refs can sometimes be more complicated than debugging state-related problems.
  • Not suitable for array or object updates: Although you can store arrays or objects in a ref, updating individual properties or elements within them won't trigger re-renders. You'll need to manually update the entire array or object and trigger a re-render if you want those changes to be reflected in the UI. This can require more work than using state for mutable value structures.

To mitigate these drawbacks

  • Use the useRef when necessary and prioritize managing UI-related data with state (useState or useReducer) as it automatically triggers re-renders.
  • Always ensure proper cleanup of refs when components are removed from the DOM to prevent memory leaks.
  • Leverage debugging tools like React DevTools to track ref values and their impact if your code becomes overly reliant on refs.
  • Consider alternative approaches like lifting the state up or using context to streamline state management and component communication.

#useRef vs. useState Hook

While both useRef and useState store values in React components, they serve different purposes and behave differently. Let's look at these Hooks, exploring their characteristics and how they contribute to state management in a React component.

FeatureuseRefuseState
MutableYes, the .current property can be changed directlyNo, state updates must be done through the setter function (e.g., setCount)
Persistence across rendersYes, the value persists for the lifetime of the componentNo, the value is reset on each re-render
Triggers re-renderNo, updating the .current property does not cause a re-renderYes, updating state using the setter function triggers a re-render of the component
Common use casesAccessing DOM elements, storing previous values, managing timers/intervals, storing referencesManaging UI state, storing data that directly affects the component's rendering

#useRef Hook vs createRef function

Before the Hooks era, React used createRef() for refs in their application. Let's break down their key distinctions when it comes to managing refs in React applications:

FeatureuseRef HookcreateRef Function
Component typeExclusively used in functional components (introduced in React 16.8)Designed for class components (pre-Hooks era)
Initial valueTakes an optional initial value, assigned to ref.current (defaults to undefined)No initial value, ref.current is initially null
Re-rendersReturns the same ref object on each re-render, maintaining its valueCreates a new ref object on every re-render, losing the previous value
PersistenceValue persists for the entire component lifecycleValue is lost on re-renders unless explicitly stored in a class component's instance variable
FlexibilityMore flexible in functional components due to its persistenceLess flexible, requires additional state management for persistence in class components

If you're using functional components with React Hooks, useRef is the way to go. But if you're working with legacy class components, createRef is your best option.

However, consider refactoring to functional components and the useRef Hook for a more modern and maintainable approach. Next, let's discuss the useRef Hook in data fetched from content management systems.

useRef Hook in dynamic content fetched from CMSs

The useRef Hook, although not inherently tied to Content Management Systems (CMS), plays a crucial role in enhancing user interactions with dynamically fetched content from platforms like Hygraph, a GraphQL-Native Headless Content Management, and Federation System.

By storing references to specific elements within the fetched data, such as images or content blocks, developers can implement features like lazy loading, smooth scrolling, or interactive components.

Furthermore, useRef is valuable in optimizing data fetching from CMSs by caching results, especially for complex queries or large datasets. This caching mechanism reduces the load on the CMS and improves the application's performance as it avoids redundant data fetching operations.

#Summing it up

While often overlooked, the useRef Hook is a powerful asset in a React developer's toolkit. It excels in managing references, optimizing performance, and enabling seamless interactions with DOM elements.

Understanding its capabilities and appropriate use cases allows developers to create more efficient, maintainable, and interactive React applications. Whether you're building complex animations, managing timers, or integrating with headless CMS platforms like Hygraph to optimize content delivery, useRef proves its value repeatedly.

Ready to explore the power of Hygraph? Sign up for a free-forever developer account and experience the seamless integration of Hygraph with React and its powerful Hook like useRef.

Blog Author

Motunrayo Moronfolu

Motunrayo Moronfolu

Technical writer

Motunrayo Moronfolu is a Senior Frontend Engineer and Technical writer passionate about building and writing about great user experiences.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.