Menu

React useDeferredValue: Let Slow Parts Lag Behind

useDeferredValue gives you a copy of a value that is allowed to lag behind during heavy renders, so an input stays responsive while a slow list catches up. Learn how it works, the React 19 initial value, and how it compares with useTransition and debouncing.

This page includes runnable editors - edit, run, and see output instantly.

useDeferredValue gives you a copy of a value that is allowed to lag behind. When the value changes, React first redraws the screen with the old copy, which keeps typing instant, then renders the slow part with the new value in the background.

Type a word quickly. The input keeps up and the list dims while it is behind. The Console logs one SlowList rendering line per letter, because SlowList logs as soon as React starts rendering it, but the lists for the in-between values never reach the screen: each new letter interrupted the background render, and React started over with the latest text. Change query={deferredQuery} to query={query} and type again: every keystroke now waits a quarter of a second for the list.

The syntax

const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
  • value is anything you render: a string from an input, a number, an object. Primitives work best, because a new object on every render always counts as a change.
  • The return value equals value most of the time. Right after value changes, it still holds the previous value for one render, and catches up in a background render.

How it works

Each change produces two renders:

  1. An urgent render with the new value and the old deferredValue. The input shows the new text. Components that receive only deferredValue get the same props as before.
  2. A background render with the new deferredValue. This is the slow one. React renders it in slices and abandons it if value changes again before it finishes, then starts over with the latest value.

You can watch this happen by logging both values:

Type a single letter, say a, and the Console prints two more lines: first text = "a" with deferred = "", then both "a". That first render is what keeps typing fast, but only if the slow part can skip it. That is why SlowList in the first example is wrapped in memo: during the urgent render its query prop has not changed, so React skips it. Without memo, the urgent render would still run the whole slow list, just with the old text, and you would gain nothing.

Showing stale content

While the background render is in progress, value and the deferred value differ. Compare them to tell the user the content is out of date, without hiding it:

const isStale = query !== deferredQuery;

<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
    <SlowList query={deferredQuery} />
</div>

This is usually nicer than a spinner: the old results stay readable and change in place when the new ones are ready. The same idea works with Suspense: if the deferred value makes a component suspend while it loads data, React keeps showing the old content instead of the fallback until the new content is ready (see Suspense and lazy).

The initial value (React 19)

By default the first render has nothing to lag behind, so the deferred value is the real value from the start. Since React 19 you can pass a second argument. On the first render the hook returns initialValue, and React then renders again in the background with the real value. This lets you show something cheap at once and fill in the expensive part right after.

The Console shows two renders: first with "", then with the full text. Remove the second argument and only one render is logged, with the full text straight away.

useDeferredValue vs useTransition

Both mark work as non-urgent and use the same rendering machinery. The difference is where you put them:

  • useTransition wraps the update. You call startTransition(() => setQuery(text)), so you need the setter. You get isPending for free.
  • useDeferredValue wraps the value. You call it where the value is used, so it works when the value arrives as a prop or comes from a hook you do not control. You compute "pending" yourself with value !== deferredValue.
// You own the state: either works, a transition is direct
startTransition(() => setQuery(nextQuery));

// You only receive the value
function SearchResults({ query }) {
    const deferredQuery = useDeferredValue(query);
    return <SlowList query={deferredQuery} />;
}

useDeferredValue vs debounce and throttle

Debouncing waits a fixed time after the last change; throttling updates at most once per interval. Both pick a delay up front, so on a fast laptop they make the page slower than it needs to be, and on a slow phone the delay may still be too short.

useDeferredValue has no delay. The background render starts immediately, so on a fast device the list updates almost at once, and on a slow device React keeps the input responsive and renders the list as often as it can. The background work is also interruptible, which a debounced render is not: once a debounced render starts, it blocks until it finishes.

What it does not do is cut network requests. If each new value starts a fetch, the deferred value still changes with each render that completes, so debounce the request itself when request volume matters.

Common mistakes

  • Forgetting memo. As shown above, the slow component must be able to skip the urgent render. If it is not wrapped in memo, or one of its other props is a new object on every render, it renders twice per keystroke instead of once.
  • Passing a new object. useDeferredValue({ query }) creates a new object on every render, so the deferred value always looks changed and React starts an extra background render every time. Defer the primitive (useDeferredValue(query)) and build objects from it.
  • Expecting fewer requests. A component that fetches based on the deferred value still fetches for every value that finishes rendering.

When to reach for it

Use useDeferredValue when one input drives a part of the page that is slow to render, the slow part can be memoized, and you are fine showing slightly old results for a moment: search results, a chart that redraws from a filter, a preview of formatted text. If the slow part is slow for a reason you can fix, such as rendering 5,000 rows when 50 are visible, fix that first. For the version of this pattern where you own the setter, see useTransition.

Frequently Asked Questions

What does useDeferredValue do?

It returns a version of a value that may lag behind the real one. When the value changes, React first renders with the old deferred value, then renders again in the background with the new one, and that background render can be interrupted.

Why does useDeferredValue not make my list faster?

The slow component must be wrapped in memo and receive the deferred value. Otherwise the urgent render still renders the slow component, just with the old value, and typing stays slow.

What is the difference between useDeferredValue and useTransition?

useTransition wraps the state update, so you need access to the setter. useDeferredValue wraps a value, so it works when you only receive the value, for example as a prop or from a hook you do not control.

Is useDeferredValue a debounce?

No. There is no fixed delay. React starts the deferred render right after the urgent one, and on a fast device the lag is barely visible. It also does not reduce network requests on its own.

What is the second argument of useDeferredValue?

Since React 19 you can pass an initialValue. On the first render the hook returns it, then React renders again in the background with the real value.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED