skip to content

In React, what does useDeferredValue(value) return during the render right after value changes, and what does React do next?

level: middleimportance: must knowfreq 58%

answer

  1. returns the old one first
  2. two renders per change, not one
  3. the second one can be thrown away
  4. Object.is decides what counts as changed
  5. no timer anywhere in it

basics

~20 s

useDeferredValue returns the previous value on the render where value changes, so that render is fast. React then schedules a second, non-urgent background render with the new value, and that render can be interrupted and restarted if value changes again.

solid answer

~50 s

`useDeferredValue(value)` returns a lagging copy of whatever you pass it. When `value` changes, React first re-renders with the *old* deferred value — that render is cheap and matches the urgent update — and then schedules a second render in the background where the hook returns the new value. Only that second render pays the cost of whatever depends on it. If `value` changes again before the background render finishes, React throws that work away and restarts with the newest value, so intermediate values never have to be fully rendered. Comparison is by `Object.is`, so passing a freshly created object every render defeats it. React 19 adds an optional second argument, `useDeferredValue(value, initialValue)`, which is what the hook returns on the very first render instead of `value` itself, letting the initial mount show a light placeholder and then upgrade.

code

javascript · 23 lines
javascript
import { memo, useDeferredValue, useState } from 'react';

const Results = memo(function Results({ query }) {
  const items = [];
  for (let i = 0; i < 20000; i++) {
    if (String(i).includes(query)) items.push(i);
  }
  return <p>{items.length} matches</p>;
});

export default function Search() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <Results query={deferredQuery} />
      </div>
    </div>
  );
}

go deeper

for a junior

Know the shape: one value in, a lagging copy of that value out. Say that right after the input changes you get the previous value, which keeps that render cheap.

for a middle

Explain the two-render sequence and that the background render is restarted when a newer value arrives, and state that comparison is by Object.is so the deferred value must be a stable reference.

for a senior

Demonstrate that the deferral only pays when the consumer can bail out — memoized child or stable element — and show how you surface staleness by comparing the current and deferred values rather than expecting a flag.

for a principal

Own the tradeoff that every change now costs two renders, and be able to say when that is a bad deal: cheap subtrees, low-frequency updates, or workloads where the real fix is virtualizing or narrowing what re-renders at all.

## The signature ```js import { useDeferredValue } from 'react'; function SearchResults({ query }) { const deferredQuery = useDeferredValue(query); // ... } ``` One required argument — any value: a string, number, object, or anything else. It returns a value of the same kind. In React 19 there is an optional second argument, `initialValue`, returned on the component's first render. ## The two-render pattern The hook's whole behaviour is a two-step sequence. Suppose `query` goes from `"re"` to `"rea"`: 1. **The urgent render.** React re-renders the component right away because `query` changed. During this render `useDeferredValue` returns the *old* value, `"re"`. Anything computed from the deferred value therefore does not change, so React can bail out of re-rendering the expensive subtree that receives it — provided that subtree is memoized or otherwise skippable. The result commits immediately, so an input bound to `query` shows the new character with no delay. 2. **The background render.** React then starts a second render, at non-urgent priority, in which the hook returns `"rea"`. Now the expensive subtree does re-render. When it finishes, React commits it. So one state change produces two renders. That is the cost you accept in exchange for the first one being fast. ## Interruption The background render is not privileged. If `query` becomes `"read"` while the `"rea"` render is still in progress, React discards the in-progress work and starts again with `"read"`. This is why a fast typist does not queue up one full expensive render per keystroke — the intermediate ones are abandoned before they commit. It is also why the hook is not a debounce: there is no timer, no fixed wait, and if the machine is fast enough the deferred value catches up on the very next frame. ## The comparison React uses React decides whether the value changed with `Object.is`, the same comparison used elsewhere in the hook APIs. Consequence: pass a primitive, or a reference that is stable between renders. This defeats the hook — ```js // new object identity every render, so it never settles const deferred = useDeferredValue({ query, page }); ``` — because each render supplies a value that is not `Object.is`-equal to the last, so React is perpetually chasing a new one. Defer the primitives, or memoize the object first. ## The initialValue argument (React 19) ```js const deferredQuery = useDeferredValue(query, ''); ``` On the first render the hook returns `''` instead of `query`, then immediately schedules a background render with the real `query`. That gives you a cheap first paint — an empty or skeleton state — without a separate piece of state to track mounting. Without the second argument, the first render simply returns `value` and no background render is scheduled for the mount. ## Making the deferral actually pay The hook only defers *the value*. The parent still re-renders on every change of `value`. If the expensive child re-renders anyway because it is not memoized, the deferred value bought you nothing except an extra render. The usual pairing is: ```js const List = memo(function List({ query }) { /* expensive */ }); function Page() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <input value={query} onChange={e => setQuery(e.target.value)} /> <List query={deferredQuery} /> </> ); } ``` Because `List` is memoized and receives the *deferred* string, it skips the urgent render and only re-renders in the background pass. ## Showing that the content is stale Since you hold both values, you can detect the gap yourself: `const isStale = query !== deferredQuery;` and dim the list while it is true. There is no built-in flag on this hook — that is the visible difference from `useTransition`, which hands you `isPending`. ## Frequent misconceptions It is not a debounce or a throttle; there is no delay parameter and none can be configured. It does not skip renders — it reorders them. And it does not make the expensive component cheap: if the background render itself takes a second, it still takes a second, it just no longer blocks typing.

  • How is useDeferredValue different from debouncing the value with a timer?
    A debounce inserts a fixed wait and skips the intermediate values entirely, so even on a fast machine you pay the delay. `useDeferredValue` has no timer: the background render starts immediately and catches up as fast as the device allows, and intermediate values are abandoned only because newer work supersedes them. It also cannot get stuck showing a value the user has moved past.
  • You added useDeferredValue but the expensive child still re-renders on every keystroke. Why?
    Because deferring the value does not stop the parent from re-rendering, and an unmemoized child re-renders with its parent regardless of whether its props changed. Wrap the child in `memo` (or pass it as `children` so its element identity is stable) so the urgent render can bail out; only then does the old deferred value actually save work.
  • What happens if you pass a freshly created object to useDeferredValue on every render?
    It never settles. React compares with `Object.is`, so a new object literal each render always looks like a change, and the hook keeps scheduling background renders that are immediately superseded. Defer primitive values, or memoize the object with `useMemo` so its identity is stable between renders.
  • Is there a pending flag on useDeferredValue?
    No — unlike `useTransition`, it returns only the value. You derive staleness yourself by comparing the current value with the deferred one, for example `const isStale = query !== deferredQuery`, and use that to dim or fade the lagging content. That comparison is exact and cheap for primitives.

saying these in an interview costs you the question

  • Calls it a built-in debounce with a delay you can set
  • Thinks intermediate renders are skipped rather than restarted
  • Believes it makes the slow component render faster
  • Expects it to work without memoizing the expensive child
  • Assumes it returns a pending boolean alongside the value

context