useRef & DOM access

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Refs are the non-reactive sibling of frontend/react: useState (the reactive model — refs opt OUT of it on purpose).

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.

piecerolere-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

intentpatternwhy
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