skip to content

A React 19 component does `const deferredQuery = useDeferredValue(query)` and renders a large list from deferredQuery. Walk through the renders React performs when query changes from 'ab' to 'abc'.

level: middleimportance: should knowfreq 52%

answer

  1. the state changes now, the hook lags one render
  2. two renders: old value, then new
  3. the second render is interruptible
  4. identity comparison — never a fresh object

basics

~20 s

React renders twice. First an urgent render in which the hook still returns the old value, so the fast parts of the UI update while the big list stays as it was; then a background, interruptible render in which the hook returns the new value and the list is rebuilt.

solid answer

~40 s

Setting `query` to `'abc'` schedules an urgent render. In that render `useDeferredValue` deliberately returns the *previous* value, `'ab'`, so anything reading `deferredQuery` renders exactly as before and the commit is cheap — the input and other urgent UI update instantly. React then schedules a second render at transition priority in which the hook returns `'abc'`; that render is interruptible, so if `query` changes again before it finishes React throws the partial work away and restarts with the newest value. Only when a background render completes does React commit it, replacing the list in one shot. The user therefore sees a stale-but-complete list rather than a half-updated one, and `deferredQuery !== query` is the reliable signal that a newer value is still being worked on.

go deeper

for a junior

Recall the shape: your state updates immediately, but the hook keeps handing back the old value for one render so the expensive part can catch up afterwards.

for a middle

Be able to narrate both renders in order — which value the hook returns in each, which one is interruptible — and name the identity comparison that makes fresh objects loop.

for a senior

Demonstrate that the technique only pays off when the expensive subtree can bail out on unchanged props, and that abandoned background renders mean extra render work traded for responsiveness.

for a principal

Own the choice between deferring consumption and splitting state at the source, and set the expectation that a stale-value signal is presented consistently across the product rather than invented per screen.

## What "lagging a value" means concretely `useDeferredValue` does not delay your state. `query` becomes `'abc'` the instant the setter runs. What lags is the value *that particular hook hands back* during rendering, and the lag lasts exactly one render pass. ## The render sequence For a change from `'ab'` to `'abc'`: 1. **Urgent render.** React renders the component with `query === 'abc'`. `useDeferredValue` returns `'ab'` — the value it returned last time. Everything driven by `query` (the text field) is up to date; everything driven by `deferredQuery` (the big list) receives an unchanged prop, so if that subtree can bail out it does no work at all. React commits this render immediately; the keystroke appears with no delay. 2. **Background render.** React schedules a second render of the same component at transition priority. This time the hook returns `'abc'`. The expensive list renders with the new term. Because this render is non-urgent, React may yield the main thread part-way through it to handle another keypress, a click, or a scroll-driven update. 3. **Commit or restart.** If nothing more urgent invalidates it, React finishes and commits the background render, swapping in the new list in a single commit. If `query` changes again first, React discards the in-progress render and starts over with the newest value — the intermediate list is never shown. The user never sees a partially updated list, because React commits transition renders only when they are complete. ## The comparison React makes On each render, React compares the value you pass with the one it is currently returning. On the first render of the component the hook simply returns the value you gave it (React 19 also accepts an optional second argument for a different first-render value). Once the passed value settles — the background render completed and the two now match — no further deferred render is scheduled. This comparison is by identity, which produces a well-known trap: passing an object or array created fresh during render means the value is *always* different, so React schedules a background render, which produces another new object, forever. Pass primitives, or objects created outside rendering: ```jsx // trap: a new object every render, so the deferred value never settles const deferred = useDeferredValue({ q: query, page }); // fine: primitives settle after one background render const deferredQuery = useDeferredValue(query); const deferredPage = useDeferredValue(page); ``` ## Reading the lag as a signal Because the hook returns the old value while newer work is outstanding, comparing the two tells you a background render is in flight: ```jsx const deferredQuery = useDeferredValue(query); const isStale = deferredQuery !== query; ``` That is the deferred-value counterpart to a pending flag, and it is derived rather than stored, so it cannot fall out of sync with the render it describes. ## Why the bail-out matters Step 1 is only cheap if the expensive subtree can actually skip work when its props are unchanged. If the list is rendered inline inside the same component body, the urgent render walks it anyway with the old term — you still pay the full cost, just with stale data, and the technique buys nothing. The deferred value is designed to be handed to a child that can bail out on identical props; keeping the expensive tree in a separate component that receives `deferredQuery` as a prop is what makes step 1 nearly free. ## How this differs from marking your own update Both mechanisms reach the same scheduler. The difference is where you stand relative to the update. If you own the setter, you can mark the update itself as non-urgent when you call it. `useDeferredValue` is for the case where you do not — the value arrives as a prop, or comes from a single piece of state that also drives urgent UI and cannot be split. You defer the *consumption* of a value instead of the *production* of an update. ## What the user experiences Typing `abc` quickly on a slow list produces: instant characters in the field, a list that continues to show results for whatever term last completed, and then one clean swap to the newest results when the machine catches up. No intermediate lists flash by, because the abandoned renders were never committed.

  • What does useDeferredValue return on the component's very first render?
    The value you passed, unchanged — there is no previous value to lag behind, so nothing is deferred and no background render is scheduled. React 19 accepts an optional second argument if you want a different value on that first render instead, after which it defers to the real one.
  • How can you tell, inside render, that a deferred update is still outstanding?
    Compare the two: when the deferred value is not identical to the current one, a background render for the newer value has not committed yet. Teams use that to dim or annotate the stale region. It is derived from the render itself, so it cannot drift out of sync.
  • Why does passing a freshly built object to useDeferredValue misbehave?
    The hook compares by identity. A new object each render is never equal to the previous one, so React keeps scheduling a background render, which renders again and creates yet another object. Pass primitives, or objects created outside of rendering, so the value can settle.

saying these in an interview costs you the question

  • Says the state update itself is delayed
  • Thinks it renders once with a timer in between
  • Expects a partially updated list to be committed
  • Passes a newly created object as the deferred value
  • Believes the lag persists until some timeout expires

context