What useRef actually is
useRef returns a plain object with a single mutable property, .current, that React creates once and keeps around for the lifetime of the component. Unlike state, writing to .current doesn’t ask React to re-render anything — it’s just a box sitting quietly next to your component, immune to the render cycle.
When developers actually run into this
You reach for useRef the moment you need to remember something between renders that the UI doesn’t actually need to reflect — a timer ID you’ll clear later, the previous value of a prop, or a reference to a real DOM node so you can call .focus() on it. The trouble starts when someone reaches for it as a shortcut around useState, expecting a ref update to also update what’s on screen.
The problem
function Counter() {
const countRef = useRef(0);
return (
<button onClick={() => { countRef.current++; }}>
Clicked {countRef.current} times
</button>
);
}
// Click the button 5 times, the label still reads "Clicked 0 times"
// countRef.current is actually 5 internally — React just never re-rendered to show it
The ref value is genuinely changing. Log it inside the click handler and you’ll see it increment correctly. The button label just never catches up, because nothing told React a render was needed.
The fix
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
);
}
// Click the button 5 times, the label updates: "Clicked 1 times", "Clicked 2 times", ... "Clicked 5 times"
setCount does two things a ref update never does: it schedules a re-render, and it hands the new value to that render so the JSX actually reflects it.
Two real use cases, not just the fix
The counter above shows what useRef gets wrong when misused for UI state — but the two cases it’s actually built for are worth seeing directly, since they’re the reason the hook exists at all.
Internal state that never appears in the UI, like a pending timeout you need to cancel:
function SearchBox() {
const timeoutRef = useRef(null);
function handleChange(value) {
clearTimeout(timeoutRef.current); // clear the previous pending search
timeoutRef.current = setTimeout(() => runSearch(value), 300);
}
// storing the timeout ID in a ref means changing it never triggers
// a pointless re-render — the ID itself is never shown to the user,
// it's just something the component needs to keep around between calls
}
A handle to a real DOM node, so you can imperatively call browser APIs React doesn’t expose declaratively:
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // .focus() has no JSX/prop equivalent — it's an imperative DOM method
}, []);
return <input ref={inputRef} />;
}
// React populates inputRef.current with the actual <input> DOM element after mount.
// Reading or calling methods on it doesn't touch React's render cycle at all —
// it's the same as grabbing the element with document.querySelector and calling .focus() on it
Both examples share the same underlying reason useRef fits: in neither case does the value belong in what gets rendered. A timeout ID and a DOM node reference are things the component needs to keep around, not things it needs to display — which is exactly the boundary useRef was built to sit on.
Why this happens (the deeper reason)
React’s rendering model is opt-in, not automatic — React only re-renders a component when React knows something relevant has changed. For function components, the most common way to tell React this is by calling a state setter or when new props or context values are received. A ref is deliberately outside that system. That’s not a limitation, it’s the point: some values genuinely don’t belong in the render output (a setTimeout ID, a flag for “did this effect already run once”), and forcing every mutation through useState would mean paying for a re-render you don’t need. The rule of thumb: if a value should show up in the JSX, it’s state; if it’s something the component needs to keep track of but the user never sees, it’s a ref.