useRef Explained
A mutable value that survives re-renders without causing them — for DOM access and for state that doesn't belong in the render output.
2 min read
useRef creates an object with a single mutable .current property that persists for the lifetime of the component, across every re-render:
import { useRef } from "react";
function Component() {
const countRef = useRef(0);
function handleClick() {
countRef.current += 1;
console.log(countRef.current);
}
return <button onClick={handleClick}>Log count</button>;
}The important contrast with useState: changing countRef.current does not trigger a re-render, and reading it always gives you the latest value immediately, with no batching or stale-closure behavior to worry about. Use useState for anything that should show up in the UI; use useRef for values a component needs to remember that have nothing to do with what's rendered.
Accessing DOM nodes directly
The most common use of useRef is getting a handle on a real DOM element, for things React doesn't have a declarative API for — focusing an input, measuring an element's size, or triggering a video's play():
function TextInput() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus the input</button>
</>
);
}Passing useRef(null) as the ref prop on a JSX element tells React to set .current to the actual DOM node once it's mounted. Before that first render commits, .current is null — which is why DOM refs are normally read inside event handlers or useEffect, never during render itself.
Storing values that shouldn't cause a re-render
Refs are also useful for bookkeeping that a component needs across renders but that has no business being state — a previous prop value for comparison, a setInterval ID so it can be cleared later, or a flag to avoid double-running logic:
function useMountedRef() {
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
return mountedRef;
}useRef vs. useState, at a glance
| | useState | useRef |
|---|---|---|
| Triggers re-render on change | Yes | No |
| Value available immediately after update | No (next render) | Yes |
| Use for | Anything shown in the UI | DOM handles, non-visual bookkeeping |
If you find yourself reaching for a ref just to "store a value without re-rendering" for something that actually does affect what's on screen, that's usually a sign it should be state instead — a ref update won't repaint the UI, so the display will silently fall out of sync.
With the three foundational hooks covered, the next section moves into rendering patterns: showing and hiding UI conditionally, and rendering lists correctly.