useRef Hook
Hold a mutable value that persists across renders without causing re-renders.
Search across all documentation pages
Hold a mutable value that persists across renders without causing re-renders.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card - copy-paste ready.
// Mutable instance variable
const renderCount = useRef(0);
renderCount.current += 1;
// DOM element reference
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// Store previous value
const prevValue = useRef(value);
useEffect(() => { prevValue.current = value; });When to reach for this: You need to access a DOM element directly, store a mutable value that should not trigger re-renders, or keep track of a previous value.
"use client";
import { useEffect, useRef, useState } from "react";
export function Stopwatch() {
const [elapsed, setElapsed] = useState(0);
const [running, setRunning] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (running) {
intervalRef.current = setInterval(() => {
setElapsed((prev) => prev + 10);
}, 10);
}
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [running]);
const reset = () => {
setRunning(false);
setElapsed(0);
};
const minutes = Math.floor(elapsed / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
const ms = Math.floor((elapsed % 1000) / 10);
return (
<div className="space-y-3">
<p className="text-3xl font-mono tabular-nums">
{String(minutes).padStart(2, "0")}:{String(seconds).padStart(2, "0")}.
{String(ms).padStart(2, "0")}
</p>
<div className="flex gap-2">
<button
onClick={() => setRunning((r) => !r)}
className="px-3 py-1 border rounded"
>
{running ? "Stop" : "Start"}
</button>
<button onClick={reset} className="px-3 py-1 border rounded">
Reset
</button>
</div>
</div>
);
}What this demonstrates:
useRef to store the interval ID so it can be cleared laterReturnType<typeof setInterval> provides the correct type for the timer IDuseRef returns a mutable object with a single .current property.current does not trigger a re-renderuseState, there is no setter function - you mutate .current directly| Parameter | Type | Description |
|---|---|---|
initialValue | T | Initial value assigned to .current |
| Return | Type | Description |
|---|---|---|
ref | { current: T } | Mutable ref object |
Focus an input on mount:
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;Track previous value:
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}Store latest callback (avoids stale closures):
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
// Use callbackRef.current in event handlers or timersMeasure a DOM element:
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (divRef.current) {
const { width, height } = divRef.current.getBoundingClientRect();
setSize({ width, height });
}
}, []);// DOM ref - use `null` as initial value with element type
const divRef = useRef<HTMLDivElement>(null);
// divRef.current is HTMLDivElement | null
// Mutable ref - pass the type as generic
const countRef = useRef<number>(0);
// countRef.current is number
// Distinction: useRef<T>(null) creates RefObject<T> (readonly .current)
// useRef<T | null>(null) creates MutableRefObject<T | null>
// For DOM refs, use the first pattern; for mutable values, use the secondReading refs during render - Accessing ref.current during rendering (outside useEffect or event handlers) can give inconsistent results. Fix: Read refs in effects or event handlers only.
Expecting re-renders on mutation - Updating ref.current does not re-render the component. Fix: If the UI should update, use useState instead.
Null ref on first render - A DOM ref is null until React attaches it after the first render. Fix: Access DOM refs inside useEffect or after a null check.
Ref vs. state confusion - Storing UI-visible data in a ref means the display never updates. Fix: Use refs only for values that do not need to be displayed or that drive side effects.
Callback refs vs. object refs - Object refs cannot notify you when the element changes (e.g., conditional rendering). Fix: Use a callback ref ref={(node) => { ... }} when you need to react to attachment/detachment.
| Alternative | Use When | Don't Use When |
|---|---|---|
useState | The value should trigger a re-render when it changes | You need a silent mutable container |
| Callback ref | You need to run code when a ref attaches or detaches | You just need a stable reference to an element |
document.getElementById | Outside React (rare) | Inside React components - use refs instead |
| Module-level variable | Value is shared across all component instances | Value should be per-component-instance |
Why refs instead of module variables? Module-level variables are shared across all instances of a component. Refs are per-instance - each mounted component gets its own .current.
useRef stores a mutable value that persists across renders but does not trigger re-renders when updated.useState stores a value that triggers a re-render when updated via its setter.useRef for values the UI does not display; use useState for values the UI reflects.null until React attaches it after the first render.useEffect (which runs after mount) or behind a null check.// DOM ref: use null initial, get RefObject<T> (readonly .current)
const inputRef = useRef<HTMLInputElement>(null);
// Mutable ref: include null in the generic union
const countRef = useRef<number | null>(null);
// countRef.current is number | null (mutable).current mutates the object in place; React has no way to detect this change.ref.current = newValue.useState instead.function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}useEffect runs after render.ref={(node) => { ... }} when you need to run code when an element attaches or detaches (e.g., conditional rendering).const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
intervalRef.current = setInterval(tick, 1000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);ref.current during render (outside useEffect or event handlers) can give inconsistent results..current.const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;null until mount, so access it inside useEffect with optional chaining.callbackRef.current in timers or event listeners to always call the latest version.Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥