Lesson 15 of 31
useRef for the DOM and Mutable Values
The one hook that holds a mutable value across renders without causing a re-render when it changes — for reaching a real DOM node, and remembering things the screen shouldn't reflect.
Every hook so far either produces something to render (props, state) or
runs in sync with rendering (effects). useRef is different: it hands you a
mutable box that survives across renders, without ever asking React to
re-render when its contents change.
The shape
import { useRef } from "react";
const myRef = useRef(initialValue);
// myRef is { current: initialValue }, and stays the SAME object across rendersuseRef returns a plain object with a single property, current, set to
whatever initial value you passed. That object is created once and reused
for the component's entire lifetime — mutating myRef.current doesn't
trigger a re-render, and reading it later (in a different render) sees
whatever it was last set to.
Reaching an actual DOM node
The most common use: getting a handle on a real DOM element to call an imperative browser API React doesn't expose declaratively, like focusing an input.
import { useRef } from "react";
function SearchBox() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus the input</button>
</>
);
}Passing a ref to an element's ref attribute is special-cased by React:
once that element mounts, React sets inputRef.current to the actual DOM
node. Before the first render, or if the element is conditionally not
rendered, .current is null — which is why useRef(null) (not some other
default) is the conventional starting value.
Mutable values that shouldn't cause a re-render
The second common use has nothing to do with the DOM: storing a value the component needs to remember, where changing it should not re-render anything — a timer id, a flag, the previous value of a prop.
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <p>{seconds}s elapsed</p>;
}Here, intervalRef.current holds the interval id purely so the cleanup
function can clear it later. If this were stored in state instead, setting
it would trigger a pointless extra re-render every time the component
starts a new interval — a re-render that has nothing to do with anything
actually displayed.
Refs vs. state, side by side
useState | useRef | |
|---|---|---|
| Changing it | Triggers a re-render | Does not trigger a re-render |
| Value shown in UI | Typically yes | Typically no — internal bookkeeping |
| Read during render | Safe and expected | Avoid — see below |
Don't read (or write) a ref during render
Because ref updates bypass React's rendering entirely, using a ref's value to decide what to render breaks the assumption that render is a pure, predictable function of props and state — the same assumption the reconciliation lesson relied on. Refs are meant to be read and written inside effects and event handlers — after render, in response to something that already happened — not consulted while deciding what the render itself should produce.
// Don't do this — reading a ref during render
function Weird() {
const countRef = useRef(0);
countRef.current++; // mutating during render — avoid
return <p>{countRef.current}</p>; // won't reliably update the screen anyway
}If a value needs to affect what's displayed, it needs to be state, not a ref — that's the entire reason the two hooks exist separately.
Try it yourself
What to remember
- A ref is a mutable box (
{ current: ... }) that persists across renders without ever triggering a re-render when it changes. - Pass a ref to an element's
refattribute to get a handle on the real DOM node once it mounts. - Use a ref for bookkeeping the component needs internally — timer ids, flags, previous values — that shouldn't affect what's rendered.
- Don't read or write a ref during render itself; refs belong in effects and event handlers, and anything that should affect the UI belongs in state instead.
Check yourself
4 questions · pass 3/4 to unlock Composition and Children
1.What is the key difference between state (useState) and a ref (useRef)?
2.How do you get a reference to an actual DOM node, like to call
.focus()on an input?3.Why would you use a ref instead of state to store something like a timer id or the previous value of a prop?
4.What does reading
myRef.currentduring render (not inside an effect or event handler) risk?
4 left to answer