useRef: a box that survives renders
useRef(initialValue) returns {'{ current: initialValue }'}.
The .current property is mutable and persists across every re-render —
but writing to it schedules no re-render. That is the whole
point: refs are how you reach outside React's reactive model. Two faces — attach one to
a JSX node to grab the live DOM element, or use it as a mutable instance slot (timer id,
previous value) that should never by itself repaint the screen.
| piece | role | re-renders? |
|---|---|---|
useRef(x) |
returns {'{ current: x }'}; same object identity every render |
declaring it: no |
ref={myRef} |
attach to a JSX node; React assigns the live DOM node to myRef.current after mount |
no — just wiring |
myRef.current.focus() |
direct DOM API on the node — no querySelector |
no (unless you call setState) |
myRef.current = X |
mutate the box directly (store a timer id, previous value) | never — read at access time, not snapshotted |
1 · the refs you write (edit me)
2 · Babel compiles JSX → element tree
<RefDemo/> becomes React.createElement(RefDemo).
ref={inputRef} compiles to a ref prop — React reads it,
finds the node, and assigns inputRef.current = node right after mount.
// (hit "compile & render" to see Babel's output)
3 · live React (both ref faces, proven)
Click Focus Input to call inputRef.current.focus() on the node.
Click Measure Width to read inputRef.current.offsetWidth.
Click Increment — the prevRef ref lags one render, so
Previous shows the value from before the click. The gold-check
runs all three automatically below.
focus: — · count: — · prev: — · gold: focus → measure(width>0) → increment(count 1, prev 0)
intent → pattern
| intent | pattern | why |
|---|---|---|
| focus / scroll / select | inputRef.current.focus() |
imperative DOM call — no querySelector, works with the exact node |
| measure a node | inputRef.current.offsetWidth |
read layout synchronously; wrap in useLayoutEffect to avoid flicker |
| store a timer id | intervalRef.current = setInterval(...) |
persists across renders without causing them; clearInterval on unmount |
| previous value | prevRef + useEffect set after commit |
ref lags one render → shows the value from before the latest state change |
| integrate non-React lib | pass ref.current inside useEffect |
hand the mounted node to the library once; tear down in the cleanup return |