The useRef Hook
The useRef Hook provides a way to store a value that persists across renders without causing the component to re-render when that value changes. It is most commonly used for two purposes: directly accessing a DOM element, and storing a mutable value that should not trigger a re-render.
Think of a ref as a box that can hold a value. The box exists as long as the component exists, and the value inside can be changed at any time — but React does not watch this box the way it watches state.
Importing useRef
import { useRef } from 'react';
Basic Syntax
const myRef = useRef(initialValue);
useRef returns an object with a single property: current. The current property holds the stored value and can be read or updated at any time:
const countRef = useRef(0);
console.log(countRef.current); // 0
countRef.current = 5;
console.log(countRef.current); // 5
Unlike state, changing ref.current does not cause a re-render.
Use Case 1 — Accessing a DOM Element
The most common use of useRef is to get a direct reference to an HTML element — similar to using document.getElementById() in plain JavaScript.
This is useful for focusing an input field, playing/pausing a video, scrolling to a specific element, or measuring element dimensions.
import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus(); // Directly calls .focus() on the input element
}
return (
<div>
<input ref={inputRef} type="text" placeholder="Click the button to focus" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
The ref={inputRef} attribute on the input element tells React: "Assign this DOM element to inputRef.current." After the component renders, inputRef.current holds a direct reference to the actual input element in the browser.
Use Case 2 — Storing a Value Without Triggering Re-renders
Sometimes a value needs to be stored that does not affect the UI — for example, a timer ID, a previous state value, or a flag. Using state for this would cause unnecessary re-renders. A ref stores the value without any performance cost.
import { useState, useRef } from 'react';
function StopwatchTimer() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null); // Stores the interval ID
function startTimer() {
if (isRunning) return;
setIsRunning(true);
intervalRef.current = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
}
function stopTimer() {
clearInterval(intervalRef.current);
setIsRunning(false);
}
function resetTimer() {
clearInterval(intervalRef.current);
setIsRunning(false);
setSeconds(0);
}
return (
<div>
<p>Time: {seconds}s</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
<button onClick={resetTimer}>Reset</button>
</div>
);
}
The interval ID is stored in intervalRef.current. It can be accessed at any time to clear the interval, and changing it does not cause any unnecessary re-renders.
Use Case 3 — Tracking the Previous Value of State
A ref can capture the previous value of a state variable — something state alone cannot do:
import { useState, useEffect, useRef } from 'react';
function PreviousValueTracker() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0);
useEffect(() => {
prevCountRef.current = count; // Save current value after render
});
return (
<div>
<p>Current count: {count}</p>
<p>Previous count: {prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
After each render, the useEffect updates the ref with the current count. On the next render, the ref still holds the previous value from before the update — giving access to both the current and previous state values simultaneously.
useRef vs useState — When to Use Each
- Use useState when a value change should update the UI. React watches state and re-renders the component when it changes.
- Use useRef when a value needs to be stored across renders but changing it should NOT trigger a re-render. Examples: DOM references, timer IDs, previous values, form focus control.
Important — Do Not Read or Write Refs During Rendering
Refs should only be read or modified inside event handlers, useEffect, or other callbacks — not directly during the component's render. Reading a ref during render is generally fine, but modifying it during render can cause unpredictable behavior.
Key Points
useRefreturns an object with acurrentproperty that persists across renders.- Changing
ref.currentdoes not cause a re-render. - The most common use is attaching
refto a DOM element to access it directly. - Refs are also used to store values like timer IDs that should not affect the UI.
- Use
useStatefor values that drive the UI, anduseReffor values that do not.
The next topic introduces the useContext Hook — a way to share data across multiple components without passing props through every level of the component tree.
