#useRef
From my notes: useRef is a hook that gives you a value that persists across renders without triggering a re-render when you change it. It has two jobs: holding a reference to a DOM element (imperative access), and holding mutable values that the screen doesn't depend on (timer ids, previous values, "is this the first run?").
const ref = useRef(initialValue) returns a plain object, { current: initialValue }. React gives you the same object on every render of that component, and never looks inside it. The mental model: state is a value React watches — change it and React re-renders; a ref is a pocket on the component — you can put anything in it and take it out later, and React doesn't notice. My notes then said "Below example is to persist value even when rerenders and in itself useRef doesn't cause rerenders" — the example itself was a screenshot that didn't come through, so here is one that shows exactly that.
import { useRef, useState } from "react";
function RefVsState() {
const [stateCount, setStateCount] = useState(0);
const refCount = useRef(0);
console.log(`render: state=${stateCount}, ref=${refCount.current}`);
return (
<>
<button onClick={() => { refCount.current += 1; console.log(`ref is now ${refCount.current}`); }}>
Ref +1
</button>
<button onClick={() => setStateCount(stateCount + 1)}>State +1</button>
<p>state {stateCount} / ref {refCount.current}</p>
</>
);
}
// render: state=0, ref=0
// click "Ref +1" twice → ref is now 1, ref is now 2 (screen still: state 0 / ref 0)
// click "State +1" → render: state=1, ref=2 (screen: state 1 / ref 2)- First render:
useRef(0)creates{ current: 0 }and React stores it with this component. The screen showsstate 0 / ref 0. - Clicking "Ref +1" changes
refCount.currentto1, then2. The value really changes (the logs prove it), but norender:line appears and the screen still saysref 0: mutating a ref doesn't schedule a render. - Clicking "State +1" calls a state setter, so React re-renders.
useRef(0)now returns the same object as before — the0argument is ignored after the first render — sorefCount.currentis still2. - The screen jumps to
state 1 / ref 2. The ref value had been sitting there, preserved across renders, waiting for something else to cause a render. (The test also checked that the ref object is identical across renders:true.) - Rule of thumb: if the value is shown on screen, it's state. If it's only used by event handlers or effects, a ref is fine.
useState | useRef | |
|---|---|---|
| Returns | [value, setValue] | { current: value } |
| Changing it re-renders? | Yes | No |
| Mutable? | No — call the setter with a new value | Yes — assign ref.current |
| Read during render? | Yes, that's the point | Avoid (except lazy init) |
| Typical use | Anything the UI shows | DOM nodes, timer ids, previous values, latest callbacks |
Conceptually, useRef is useState with a setter you never call:
function useRef(initialValue) {
const [ref] = useState(() => ({ current: initialValue }));
return ref;
}useStatewith an initialiser function creates the object once, on the first render, and returns the same object on every later render.- Because the setter is never called, React never re-renders because of it. Mutating
ref.currentchanges a property of an object React isn't tracking. - This is why a ref behaves like an instance field on a class (
this.something) — which is how the React docs describe it.