Why do we need the useRef hook in React?
`useRef` stores a mutable value that persists across renders without triggering re-renders. Two main uses: (1) hold a reference to a DOM node (`ref={ref}`) for measurement/focus/imperative ops, (2) store any mutable value (timer ids, latest values, previous props) that shouldn't cause re-renders. `.current` is the only field; updating it is intentional, not a state change.
Two distinct uses
1. DOM refs
function Input() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { inputRef.current?.focus(); }, []);
return <input ref={inputRef} />;
}ref={inputRef} assigns the DOM node to inputRef.current after mount.
2. Mutable values that don't cause re-renders
function Stopwatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef<number | null>(null);
useEffect(() => {
intervalRef.current = window.setInterval(() => setTime((t) => t + 1), 1000);
return () => clearInterval(intervalRef.current!);
}, []);
return <p>{time}</p>;
}The interval id needs to survive between renders but mustn't cause re-renders when stored.
vs useState
| useRef | useState | |
|---|---|---|
| Triggers re-render | No | Yes |
| Value preserved across renders | Yes | Yes |
| Update | ref.current = ... | setX(...) |
If reading the value should NOT cause UI updates, use useRef. If the UI must reflect the value, use useState.
Common patterns
Previous value
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
}Latest value (escape stale closure)
function useLatest<T>(value: T) {
const ref = useRef(value);
useEffect(() => { ref.current = value; });
return ref;
}
function Search({ query, onSearch }) {
const onSearchLatest = useLatest(onSearch);
useEffect(() => {
const id = setInterval(() => onSearchLatest.current(query), 1000);
return () => clearInterval(id);
}, []); // safe — reads latest via ref
}Avoids stale-closure bugs without putting the callback in deps.
Caching expensive value
function Lazy() {
const expensiveRef = useRef<Result | null>(null);
if (expensiveRef.current === null) {
expensiveRef.current = computeExpensive();
}
return <Result data={expensiveRef.current} />;
}Initialize once; avoid recomputing on every render. (React 19 has use for this; useRef pattern works in 18.)
Imperative handle to a child
See [[what-does-forwardref-do-and-when-would-you-reach-for-useimperativehandle]].
Anti-patterns
- Using useRef when state is what you need (UI should update — use useState).
- Storing render-affecting data in a ref (UI won't update).
- Mutating ref.current inside render — should be in effect or event handler.
- Using ref as poor-man's state management.
Strict Mode
useRef is stable across the dev double-mount; .current is preserved. But effects re-run, so don't put one-time side effects in effects without idempotent design.
Interview framing
"useRef is for mutable values that persist across renders without triggering re-renders. Two main uses: a ref to a DOM node for imperative ops (focus, measure, scroll), and a mutable container for things like interval ids, previous values, latest-value escape hatches for stale closures, and one-time initialization. The contrast with useState: useState triggers re-renders when set; useRef does not. So if updating shouldn't update the UI, useRef; if it should, useState. Don't mutate refs inside render — do it in effects or event handlers."
Follow-up questions
- •Why doesn't useRef trigger re-renders?
- •Show me the latest-ref pattern for callbacks.
- •When would useState be wrong and useRef right?
Common mistakes
- •useRef when state is needed.
- •Mutating ref.current in render.
- •Using ref as state-management substitute.
Performance considerations
- •Cheaper than state for non-rendering values. No re-render cost.
Edge cases
- •Refs in Strict Mode (preserved).
- •Refs in SSR (null until hydration).
- •Lazy initialization patterns.
Real-world examples
- •DOM focus, measure, scroll, intervals, latest-value refs, prev-value comparisons.