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.
| piece | role | analogy |
|---|---|---|
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
| intent | pattern | why |
|---|---|---|
| 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 |