useDeferredValue

Defers part of the UI so an expensive view lags behind while the rest of the page stays responsive.

Signature
const deferredValue = useDeferredValue(value, initialValue?)
  • Added in React 18.0.0

Parameters

ParameterTypeDescription
$valueanyThe value you want to defer. It can have any type. Pass a primitive, or an object created outside of rendering — an object created during render is different every time and causes needless background re-renders.
$initialValueoptionalanyA value to use during the initial render of the component. If omitted, useDeferredValue does not defer during the initial render, because there is no previous version of value to render instead.

Return value

anyDuring the initial render, the returned deferred value is the initialValue, or the same as the value you provided. During updates, React first attempts a re-render with the old value (returning the old value), then tries another re-render in the background with the new value (returning the updated value).

Errors

Throws an invalid-hook-call error if used outside a component or custom Hook body, or inconsistently across renders because it was placed inside a condition or loop.

Changelog

  • PHP 19.0.0Added the optional initialValue argument, which lets the hook defer on the initial render too by rendering initialValue first and scheduling a background render with the real value.

useDeferredValue lets one part of the screen update behind the rest. When the value changes, React first re-renders with the previous value so the urgent part of the UI commits immediately, then re-renders in the background with the new one. That background render is interruptible: keep typing and React throws it away and restarts.

When to reach for it

  • A search or filter input where the results list is expensive to render. The input stays at full typing speed and the list catches up.
  • A value that arrives as a prop or from a Hook you do not own — you cannot wrap someone else's set call in a Transition, but you can defer the value you were handed.
  • With <Suspense>, to keep the previous content on screen instead of flashing a fallback while new data loads.

What trips people up

Deferring alone does not make anything faster. If the expensive child is not wrapped in memo, it re-renders during the urgent pass too and you gain nothing. Memoize the child and pass it the deferred value.

Compare value with the deferred one to detect staleness and dim the list — do not add a delay of your own. There is no fixed timeout here; React starts the background render as soon as the urgent one commits.

Finally, it does not prevent network requests. It is a rendering priority tool, not a debounce.

Example

Try it yourself
Loading editor…
Loading React…

When to use it

  • Keep a search box typing at full speed while a large result list re-renders behind it.
  • Show the previous chart while a new dataset renders, dimming it until the deferred value catches up.
  • Defer a prop you receive from a parent or third-party Hook whose set function you cannot reach.
  • Avoid a Suspense fallback flash on every keystroke by handing the deferred query to the suspending component.

Frequently asked questions

Is useDeferredValue the same as debouncing?

No. Debouncing waits a fixed delay before doing the work; useDeferredValue starts the background render immediately and simply lets urgent updates interrupt it. It also adapts to the device — a fast machine shows no lag at all.

Why is my UI still laggy after adding useDeferredValue?

Because the expensive component is not wrapped in memo, so it re-renders during the urgent pass anyway. Wrap it in memo and pass it only the deferred value.

When should I use useDeferredValue instead of useTransition?

Use useDeferredValue when you only have the value, not the set function that produced it — a prop, a context value, or a third-party Hook result. If you own the update, wrapping it in useTransition is more direct and gives you isPending.

More examples

Deferring on the initial render with initialValue

The first paint reads "Showing results for “nothing yet”" because the deferred value starts as an empty string; a background render immediately follows with "react hooks". This is the React 19 initialValue argument — it lets an expensive subtree stay out of the critical first paint, which the two-argument form is the only way to achieve.

Example · jsx
import { useDeferredValue, useState } from "react";

function Results({ query }) {
  return <p>Showing results for "{query || "nothing yet"}"</p>;
}

function Search({ value }) {
  // On the first render this is "", then React schedules a background
  // render with the real value. Without initialValue there is nothing
  // to fall back to, so the first render is not deferred at all.
  const deferred = useDeferredValue(value, "");
  return <Results query={deferred} />;
}

export default function Page() {
  const [query, setQuery] = useState("react hooks");

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Search value={query} />
    </div>
  );
}

Keeping stale content instead of a Suspense fallback

The list shows "react · 1" and "react · 2". The "Loading…" fallback appears only on the very first load: because the query is deferred, later keystrokes suspend in a background render, and React keeps the previous list on screen until the new data resolves rather than replacing it with the fallback.

Example · jsx
import { Suspense, use, useDeferredValue, useState } from "react";

const cache = new Map();

function fetchResults(query) {
  if (!cache.has(query)) {
    // Stands in for a real request.
    cache.set(query, Promise.resolve([`${query} · 1`, `${query} · 2`]));
  }
  return cache.get(query);
}

function Results({ query }) {
  const rows = use(fetchResults(query));
  return (
    <ul>
      {rows.map((row) => (
        <li key={row}>{row}</li>
      ))}
    </ul>
  );
}

export default function SearchPage() {
  const [query, setQuery] = useState("react");
  const deferredQuery = useDeferredValue(query);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Suspense fallback={<p>Loading…</p>}>
        <Results query={deferredQuery} />
      </Suspense>
    </div>
  );
}

Discussion

  • Be the first to comment on this lesson.