Topic 32 of 54
UseRef vs useState
Overview
`useRef` has a secret second superpower: it can hold *any* mutable value, not just DOM nodes. It is essentially an 'instance variable' for functional components. The critical difference between `useRef` and `useState` is that updating a Ref does NOT trigger a component re-render. If you have a value that changes rapidly (like a timer ID) but doesn't affect the UI visually, putting it in state is a massive performance flaw. Put it in a ref instead.
Syntax
`useRef` acts like a box that you can put anything into. The contents (`.current`) persist across renders, but mutating them is completely invisible to React's rendering engine.
State triggers Render, Ref does NOT
jsx
function RenderCounter() {
const [stateCount, setStateCount] = useState(0);
const refCount = useRef(0);
const handleState = () => {
// Updates value AND re-renders the screen (shows new value)
setStateCount(stateCount + 1);
};
const handleRef = () => {
// Updates value in memory, but NO RE-RENDER!
// The screen will NOT update, even though the number increased under the hood.
refCount.current += 1;
console.log("Ref is now:", refCount.current);
};
return (
<div>
<p>State: {stateCount} | Ref: {refCount.current}</p>
<button onClick={handleState}>Update State</button>
<button onClick={handleRef}>Update Ref</button>
</div>
);
}Common Pitfalls
- Using a Ref to store a value that *is* rendered on the screen. Since mutating the ref doesn't trigger a render, the UI will become out of sync with the data.
- Forgetting to access the value via the `.current` property (e.g., typing `myRef = 5` instead of `myRef.current = 5`).
Interview Tips
- A classic interview question: 'How do you store a value across renders without causing a re-render?' Answer: `useRef`.
Real-World Example
Storing a `setInterval` ID so you can clear it later, without causing the component to re-render just to save the ID.
example
jsx
function Stopwatch() {
const [time, setTime] = useState(0);
const timerId = useRef(null); // Perfect use case for useRef
const start = () => {
// We don't want setting the ID to trigger a render
timerId.current = setInterval(() => setTime(t => t + 1), 1000);
};
const stop = () => {
clearInterval(timerId.current);
};
}