useDeferredValue — defer the re-render

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ The declarative sibling of useTransition: useTransition marks an update non-urgent (imperative); useDeferredValue marks a value lag-behind (declarative).

useTransition → useDeferredValue: the same idea, two entry points

Both hooks split a render into an urgent part (must paint now) and a deferrable part (can lag). useTransition hands you startTransition so you wrap the state update. useDeferredValue instead takes a value and returns a lagging copy of it — React renders with the old copy first, then re-renders in the background once the new value is ready. Reach for it when the value arrives from props or a library you can't wrap in a transition.

pieceroleanalogy
value the input you hand to the hook the live needle on a speedometer
deferredValue a lagging copy React returns (old on the urgent render, new on the background render) the smoothed-out needle that catches up a beat later
urgent render new value, OLD deferredValue the dashboard repaints instantly; the heavy chart is frozen on the previous frame
background render new value AND new deferredValue (interruptible) the chart re-renders quietly and swaps in when done

1 · the deferred-value component you write (edit me)

2 · Babel compiles JSX → element tree

<SearchApp/> becomes React.createElement(SearchApp). Each keystroke fires setQuery → React re-renders with the OLD deferredQuery first, then schedules a background render with the new one.

// (hit "compile & render" to see Babel's output)

3 · live React (the deferred render, proven)

Type in the box: the immediate value snaps to your keystroke at once, while the deferred value lags behind and the list re-filters when it catches up. The gold-check does this automatically: asserts 300 items on first paint, types "Item 5", asserts the immediate value is "Item 5" right away, then waits for the deferred value to catch up and the list to re-filter to 11 items.

items: · gold: 300 items → type "Item 5" → immediate="Item 5" → deferred catches up → 11 items

intent → pattern

intentpatternwhy
keep an input snappy while a heavy list re-filters var d = useDeferredValue(q) + pass d to the slow child urgent render keeps the old d; list lags, input stays instant
make the urgent render actually cheap wrap child in React.memo + useMemo(() => filter(d), [d]) while d is unchanged, rows ref is identical → memo skips
signal that the visible data is stale var isStale = q !== d → dim with opacity user sees the list is catching up, not frozen/broken
defer a value that arrives on first paint too useDeferredValue(q, initialQ) (React 19) initialValue is used for the initial render only
show old data while new data loads (Suspense) pass d into a <Suspense> subtree no fallback flash — old deferred value stays until new resolves