2024-09-24 Web Development
Understanding useRef in React
By O. Wolfson
In React, managing component renders efficiently is crucial for building high-performing applications. A render in React refers to the process of updating the virtual DOM, a lightweight copy of the actual DOM, based on changes in data or state. The virtual DOM is innovative because it allows React to compare (or "diff") the old and new virtual DOM versions to identify exactly which parts of the real DOM need to be updated. This makes React more efficient than directly manipulating the DOM, which can be slow and costly. While React optimizes updates, minimizing unnecessary renders is key to boosting performance, as rendering too often can lead to wasted processing power and slower UI updates.
This article focuses on useRef
, a hook that offers an efficient way to manage values and DOM elements in React without triggering re-renders. useRef
solves the problem of excessive renders by allowing you to store values that persist across renders without affecting the component lifecycle. By understanding useRef
and its applications, you can optimize your React components, improve performance, and build more responsive applications.
What is useRef?
useRef
is a React hook that allows you to store mutable values that persist across renders without triggering a re-render of the component. This makes useRef
particularly useful for situations where you need to manage data or manipulate the DOM, but don’t want React to re-render the component every time the value changes.
The two primary use cases for useRef
are:
- Direct DOM Manipulation:
useRef
provides a reference to a DOM element, allowing you to interact with it directly, bypassing React’s render cycle. - Persisting Values Across Renders:
useRef
allows you to track values that should not trigger re-renders, like timers, counters, or scroll positions.
Key Characteristics of useRef:
- Does not trigger a render: Unlike
useState
, changes to theuseRef
value do not cause the component to re-render. - Direct DOM manipulation: You can directly interact with a DOM element without involving React’s rendering mechanism.
- Persistent across renders: Values stored in
useRef
survive through component re-renders, maintaining their state without causing React to update the UI.
How useRef Works
1. Storing Values without Re-Renders
With useState
, any update to the state triggers a re-render of the component. This is useful when the UI needs to reflect changes, but it can cause excessive renders when managing values that don’t affect the UI.
useRef
allows you to store values (like counters or previous states) that persist across renders without affecting the component’s lifecycle. Here’s a simple example:
tsximport React, { useRef } from "react";
function CounterWithRef() {
const countRef = useRef(0); // Track count without re-rendering
const handleClick = () => {
countRef.current += 1;
console.log("Count value:", countRef.current); // Check console for updates
};
return <button onClick={handleClick}>Click me</button>;
}
In this example, useRef
is used to store the count. Clicking the button updates the count without triggering a re-render. The console logs the new count, but the UI remains unchanged. This approach is especially useful when tracking values like scroll position, timers, or form input states that don’t need to be reflected in the UI.
2. Direct DOM Manipulation
One of the most common use cases for useRef
is directly interacting with DOM elements. React discourages imperative manipulation of the DOM but offers useRef
as a way to safely access and modify DOM elements without triggering a re-render.
For example, focusing an input field imperatively:
tsximport React, { useRef } from "react";
function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus(); // Directly focus the input element
}
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Click button to focus" />
<button onClick={handleFocus}>Focus the input</button>
</div>
);
}
Here, useRef
is used to reference the input element in the DOM. When the button is clicked, the input field is focused without causing the component to re-render. The focus ring appears because the browser directly handles the DOM update, but React’s virtual DOM remains unaffected.
How useRef Differs from useState
The primary difference between useRef
and useState
lies in how they handle updates:
useState
triggers re-renders: Any update to the state usinguseState
causes React to re-render the component, which is necessary when the UI needs to reflect the updated state.useRef
does not trigger re-renders: Changes to the.current
property ofuseRef
do not cause the component to re-render. This is ideal for tracking values that don’t need to be reflected in the UI, like timers, previous states, or DOM element references.
Why useRef Can Improve Performance
Using useRef
strategically can improve your app’s performance by avoiding unnecessary renders, especially in scenarios where the value being tracked doesn’t impact the UI.
1. Tracking Non-Visual Values
For values like timers or scroll positions, using useState
would cause frequent re-renders, potentially degrading performance. useRef
allows you to store and update these values without React re-rendering the component.
Example: Tracking Scroll Position
tsximport React, { useRef, useEffect } from "react";
function ScrollTracker() {
const scrollPosRef = useRef(0); // Track scroll position without re-renders
useEffect(() => {
const handleScroll = () => {
scrollPosRef.current = window.scrollY;
console.log("Scroll Position:", scrollPosRef.current);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <p>Scroll Position: (check console)</p>;
}
In this example, the scroll position is tracked using useRef
, and the value is logged to the console. The component doesn’t re-render on each scroll, which is especially useful when dealing with large pages or frequently updated data.
2. Preventing Performance Bottlenecks in Forms
In complex forms where every keystroke can trigger validation or state changes, using useState
could lead to excessive re-renders. With useRef
, you can track the input value without causing unnecessary renders, ensuring the form remains responsive.
Example: Efficient Input Handling
tsximport React, { useRef, useState } from "react";
function FormWithRef() {
const valueRef = useRef("");
const [error, setError] = useState("");
const validate = (val: string) => {
if (val.length < 5) {
setError("Too short");
} else {
setError("");
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
valueRef.current = e.target.value;
validate(valueRef.current); // Validate without triggering a re-render
};
return (
<div>
<input type="text" onChange={handleChange} />
<p>{error}</p>
</div>
);
}
In this example, the input value is tracked using useRef
, and the component only re-renders when the validation error needs to be updated.
Conclusion
In React, useRef
provides a powerful way to manage persistent values and directly manipulate the DOM without triggering unnecessary re-renders. By understanding when and how to use useRef
, you can optimize performance in your applications, especially in scenarios involving DOM manipulation, timers, form handling, and non-visual data.
While useState
is ideal for managing state that affects the UI, useRef
excels at tracking values that don’t need to be reflected in the UI, providing a more efficient approach in many cases. By leveraging useRef
, you can reduce render cycles, avoid performance bottlenecks, and build more responsive applications.