Explain how useState triggers re-renders and is used for UI state, while useRef holds a mutable value that persists across renders without re-rendering — and when each is the correct tool (DOM refs, timers, previous values, avoiding stale closures, and render-driving state).
Use this React interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
What’s the difference between useRef and useState? When should each be used?Frontend interview answer
This React interview question tests whether you can explain useRef vs useState in React: what is the difference, connect it to production trade-offs, and handle common follow-up questions.
- useRef vs useState in React: what is the difference explanation without falling back to memorized docs wording
- Hooks and State reasoning, edge cases, and production failure modes
- How you would answer the most likely React interview follow-up
Short answeruseState is for data that should drive what you render (changing it re-renders). useRef is for data you want to persist across renders but changing it should not trigger a re-render (a mutable container).
Hook | What it stores | What happens when it changes |
|---|---|---|
useState | A render-driving value (UI state) | ✅ Triggers a re-render |
useRef | A mutable container: { current: ... } | ❌ Does NOT trigger a re-render |
First-principles mental model
React re-renders when you want the UI to reflect new data. So ask yourself one question:
"Should changing this value update what the user sees right now?"
If yes → useState.
If no (but you still need the value to persist) → useRef.
function Example() {
const [count, setCount] = useState(0); // UI should update when this changes
const clicksRef = useRef(0); // internal bookkeeping; UI doesn't need it
const onClick = () => {
clicksRef.current += 1; // won't re-render
setCount((c) => c + 1); // will re-render
};
return (
<button onClick={onClick}>
Count: {count}
</button>
);
}
Common useState use cases
Use useState when the value affects rendering: text, visibility, selection, form inputs, loading state, errors, filters, toggles, etc.
Scenario | Correct tool | Why |
|---|---|---|
Show/hide a modal | useState | UI must update immediately |
Form input value | useState | Render depends on current value |
Loading / error / fetched data | useState | UI should reflect async results |
Selected tab / filter | useState | Directly affects what is shown |
Common useRef use cases
Use useRef when you need a value to persist, but you don’t want a render every time it changes.
Scenario | Why useRef fits | Example |
|---|---|---|
DOM element access | Imperative access to a node | focus(), scrollIntoView() |
Timer / interval IDs | Store mutable IDs across renders | setTimeout / setInterval cleanup |
Latest value for async callbacks | Avoid stale closures without rerendering | read latest state in an interval |
Previous value tracking | Persist last render's value | prevCount.current |
function Search() {
const inputRef = useRef(null);
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>
Focus input
</button>
</div>
);
}
The big trap: using useRef as "state"
People do this to avoid re-renders, but it’s usually a bug factory. If the UI depends on it and you store it in a ref, the UI won’t update and you’ll get out-of-sync screens.
function Bad() {
const valueRef = useRef('');
return (
<div>
<input onChange={(e) => (valueRef.current = e.target.value)} />
<p>Typed: {valueRef.current}</p> {/* ❌ UI won't update reliably */}
</div>
);
}
function Good() {
const [value, setValue] = useState('');
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<p>Typed: {value}</p> {/* ✅ UI updates */}
</div>
);
}
Rule of thumb that wins interviews
useState = "React should re-render when this changes."
useRef = "I need to remember something between renders, but it’s not part of the UI."
Bonus: combining both (best of both worlds)
Sometimes you want rendering plus a latest value for async callbacks (avoiding stale closures). Keep the UI in state and mirror it into a ref:
function Example() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
useEffect(() => {
const id = setInterval(() => {
console.log('latest:', countRef.current);
}, 1000);
return () => clearInterval(id);
}, []);
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}
useState holds render-driving state and triggers re-renders. useRef holds a mutable value that persists across renders but does not trigger re-renders. Use state for anything that affects UI, and refs for DOM access, timers, latest values in async callbacks, and other non-visual bookkeeping.
Use this as one explanation rep, then continue with the React interview questions cluster or a guided prep path.