Reach for ref Instead of useState Whenever You Can!
We all love useState: straightforward, declarative, reliable. Yet that reliable re-render is exactly what bites when you only need to stash a value. 👀
Let’s walk through the moments when useRef keeps your component snappy by skipping unnecessary updates.
Whenever you set state, React rerenders the component. For controlled inputs that’s fine. But for timers, interim values, or DOM handles it’s just extra work.
If a value never appears in the markup, it probably shouldn’t live in state. “Previous request ID” has nothing to do with what the user sees — pop it into a ref instead.
const timerRef = useRef<number | null>(null);
useEffect(() => {
timerRef.current = window.setInterval(() => {
// ... interval logic
}, 1000);
return () => {
if (timerRef.current !== null) {
window.clearInterval(timerRef.current);
}
};
}, []);The timer ID persists between renders, but doesn’t touch the UI. ref keeps the component quiet and lets us clean up on unmount.
const previousQueryRef = useRef<string>('');
useEffect(() => {
previousQueryRef.current = query;
}, [query]);
const isRepeated = previousQueryRef.current === query;We need the history for logic, not for rendering. ref is a perfect fit.
const inputRef = useRef<HTMLInputElement>(null);
const focus = () => {
inputRef.current?.focus();
};useRef gives a stable pointer to a DOM node. Focus it, scroll it, measure it — no virtual DOM diff required.
const chartInstanceRef = useRef<Chart | null>(null);
useEffect(() => {
chartInstanceRef.current = initChart(canvasElement);
return () => chartInstanceRef.current?.destroy();
}, []);A chart instance, map object, or media player can be stored in ref. Keep the UI calm while the library does its job.
If it changes the visual output, let it live in state. Predictability beats micro-optimization here.
| Task | useState | useRef |
|---|---|---|
| Controlled UI | ✅ | 🚫 |
| DOM references | 🚫 | ✅ |
| Timers & intervals | 🚫 | ✅ |
| External instances (charts, maps) | 🚫 | ✅ |
| Caching values between renders | 🚫 | ✅ |
useRef isn’t a dumping ground. It’s a conscious choice to separate reactive data from plain storage. If the value doesn’t touch the UI, shove it into a ref and keep the component blazing fast. 🚀