Mastering useRef in React
Learn how useRef works in React — DOM access, persisting values across renders, and common patterns that avoid unnecessary re-renders.
What you'll learn
- ✓What useRef does and how it differs from useState
- ✓How to access and manipulate DOM elements with refs
- ✓Common patterns for persisting values without re-renders
- ✓When to use useRef vs useState vs useCallback
Prerequisites
- •Familiarity with React hooks (useState, useEffect)
- •Basic understanding of the React rendering cycle
useRef is one of those hooks that looks simple on the surface but unlocks patterns you cannot achieve with state alone. It gives you a mutable container that persists across renders without triggering a re-render when it changes.
The basics
useRef returns an object with a single property: current.
import { useRef } from 'react';
function Counter() {
const renderCount = useRef(0);
renderCount.current += 1;
return <p>This component has rendered {renderCount.current} times.</p>;
}
The key difference from useState: updating ref.current does not cause a re-render. The value is updated immediately, synchronously, and React is completely unaware of the change.
Accessing DOM elements
The most common use case. Pass a ref to any built-in element’s ref prop and React populates current with the underlying DOM node after mount.
function TextInput() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
inputRef.current.select();
}
return (
<>
<input ref={inputRef} type="text" placeholder="Click the button..." />
<button onClick={handleClick}>Focus input</button>
</>
);
}
React sets ref.current to null when the element unmounts, so you always get either the real DOM node or null.
Measuring elements
Refs let you measure layout properties the DOM API exposes but React does not.
function MeasuredBox() {
const boxRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
if (boxRef.current) {
const { width, height } = boxRef.current.getBoundingClientRect();
setDimensions({ width, height });
}
}, []);
return (
<div ref={boxRef} style={{ padding: '2rem', background: '#f0f0f0' }}>
This box is {dimensions.width.toFixed(0)}px wide and{' '}
{dimensions.height.toFixed(0)}px tall.
</div>
);
}
Persisting values across renders
Any value that should survive re-renders but should not trigger them belongs in a ref.
Storing a timer ID
function Stopwatch() {
const [elapsed, setElapsed] = useState(0);
const intervalRef = useRef(null);
function start() {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setElapsed(prev => prev + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>{elapsed}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
If you stored the interval ID in state, every setIntervalId(...) call would trigger a re-render for no visible change.
Tracking the previous value
A classic pattern — compare the current prop or state value with its previous value.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function PriceDisplay({ price }) {
const prevPrice = usePrevious(price);
const direction = price > prevPrice ? '↑' : price < prevPrice ? '↓' : '—';
return (
<span>
${price} {direction}
</span>
);
}
Callback refs
When you need to run logic at the moment a DOM node attaches or detaches, pass a function instead of a ref object.
function AutoFocusInput() {
const callbackRef = useCallback((node) => {
if (node !== null) {
node.focus();
}
}, []);
return <input ref={callbackRef} type="text" />;
}
Callback refs fire on every mount and unmount. They are useful when the target element is conditionally rendered and useEffect would fire too late.
Forwarding refs to child components
React does not let you pass ref as a prop to a function component. Use forwardRef (or in React 19+, just accept ref as a prop).
const FancyInput = forwardRef(function FancyInput(props, ref) {
return <input ref={ref} className="fancy" {...props} />;
});
function Parent() {
const inputRef = useRef(null);
return <FancyInput ref={inputRef} placeholder="Fancy..." />;
}
In React 19, forwardRef is no longer required — ref is passed as a regular prop:
function FancyInput({ ref, ...props }) {
return <input ref={ref} className="fancy" {...props} />;
}
useRef vs useState
| Feature | useRef | useState |
|---|---|---|
| Triggers re-render | No | Yes |
| Update timing | Synchronous | Batched, async |
| Persists across renders | Yes | Yes |
| Readable in render | Yes (but stale if just changed) | Yes (current value) |
Rule of thumb: if the user needs to see the value on screen, use state. If only your code needs the value, use a ref.
Common mistakes
Reading a ref during render to display its value. The value is correct, but React will not re-render when it changes, so the display goes stale.
// Bug: count shows stale value
function BadCounter() {
const count = useRef(0);
return <button onClick={() => count.current++}>{count.current}</button>;
}
Setting a ref in the render body and expecting it to be visible to concurrent features. Refs are escape hatches from React’s model. Keep writes inside effects or event handlers.
Summary
useRef gives you a stable, mutable box that lives for the entire lifetime of a component. Use it for DOM access, timer IDs, previous values, and any data that should not trigger a re-render. Reach for useState the moment the value needs to appear in the UI.
Related articles
- React Building Custom React Hooks
Extract stateful logic into reusable custom hooks. The use-prefix rule, three examples (useLocalStorage, useDebounce, useToggle), tuple vs object return values, and how to test them.
- React React Custom Hooks: Patterns for Reusable Logic
Master React custom hooks with practical patterns for data fetching, form handling, debouncing, and more. Learn when and how to extract reusable logic.
- React React Higher-Order Components vs Hooks: Migration Guide
Compare Higher-Order Components and hooks in React with side-by-side examples, migration strategies, and guidance on when each pattern still makes sense.
- React React useOptimistic: Instant UI Updates Before the Server
Learn React's useOptimistic hook for optimistic UI updates: how it works, when to use it, rollback strategies, and real-world patterns with Server Actions.