React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

concurrencydeferred-renderingperformancehooks

useDeferredValue Hook

Defer updating a part of the UI to keep the rest responsive during expensive renders.

Recipe

Quick-reference recipe card — copy-paste ready.

const deferredQuery = useDeferredValue(query);
 
// The component using deferredQuery re-renders at lower priority
<SearchResults query={deferredQuery} />
 
// Detect stale content
const isStale = deferredQuery !== query;

When to reach for this: You have a fast-changing value (like search input) driving an expensive child render, and you want the input to stay responsive while the child catches up.

Working Example

"use client";
 
import { memo, useDeferredValue, useState } from "react";
 
const HeavyList = memo(function HeavyList({ query }: { query: string }) {
  const items = Array.from({ length: 5000 }, (_, i) => `Result ${i}: ${query}`);
  return (
    <ul className="max-h-64 overflow-y-auto text-sm">
      {items
        .filter((item) => item.toLowerCase().includes(query.toLowerCase()))
        .slice(0, 100)
        .map((item, i) => (
          <li key={i} className="py-0.5">{item}</li>
        ))}
    </ul>
  );
});
 
export function DeferredSearch() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);
  const isStale = deferredQuery !== query;
 
  return (
    <div className="space-y-3">
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Type to search..."
        className="border rounded px-3 py-2 w-full"
      />
      <div className={isStale ? "opacity-50 transition-opacity" : "transition-opacity"}>
        <HeavyList query={deferredQuery} />
      </div>
    </div>
  );
}

What this demonstrates:

  • query updates instantly on every keystroke, keeping the input responsive
  • deferredQuery lags behind, so HeavyList re-renders at a lower priority
  • React.memo on HeavyList is essential — without it, the component re-renders with query anyway
  • Visual staleness feedback via opacity when deferredQuery !== query

Deep Dive

How It Works

  • useDeferredValue accepts a value and returns a deferred copy of it
  • On the initial render, the deferred value equals the original value
  • When the original value changes, React first re-renders with the old deferred value, then schedules a background re-render with the new value
  • The background re-render is interruptible — if the original value changes again, React abandons the in-progress render and starts a new one
  • This works with Suspense: if the deferred value causes a component to suspend, the user sees the old content while the new content loads

Parameters & Return Values

ParameterTypeDescription
valueTThe value you want to defer
initialValueT (React 19)Optional initial value for the first render
ReturnTypeDescription
deferredValueTThe deferred version of the value

Variations

Deferring Suspense content:

function App() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);
 
  return (
    <>
      <SearchInput value={query} onChange={setQuery} />
      <Suspense fallback={<Skeleton />}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

React 19 initial value (avoid showing stale content on mount):

// Show empty results initially, then fill in at low priority
const deferredItems = useDeferredValue(items, []);

Combining with transition for loading state:

const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
 
return (
  <div>
    {isStale && <p className="text-sm text-gray-400">Loading...</p>}
    <Results query={deferredQuery} />
  </div>
);

TypeScript Notes

// Type is inferred from the input value
const deferredQuery = useDeferredValue(query); // string
 
// React 19 with initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);

Gotchas

  • Forgetting React.memouseDeferredValue only helps if the child component is memoized. Otherwise, the child re-renders with the current value on the first pass anyway. Fix: Wrap the expensive child in React.memo.

  • Not suitable for throttling API callsuseDeferredValue defers rendering, not the value change itself. It does not reduce the number of fetch calls. Fix: Use debounce for API calls; use useDeferredValue for render performance.

  • Stale content flash — The deferred value lags behind the real value, so users see stale content briefly. Fix: Show a visual indicator (opacity, spinner) when deferredValue !== value.

  • Primitive values only — Passing a new object or array every render defeats the purpose, because Object.is comparison finds a new value each time. Fix: Memoize objects or arrays with useMemo before passing to useDeferredValue.

Alternatives

AlternativeUse WhenDon't Use When
useTransitionYou control when the state update happens and want to wrap it explicitlyYou receive the value as a prop and cannot control when it changes
DebouncingYou want to reduce the number of state updates or API callsYou want instant feedback with deferred rendering
VirtualizationThe bottleneck is rendering too many DOM nodesThe bottleneck is computation, not DOM count
Web WorkerHeavy computation should be moved off the main thread entirelyThe work is React rendering

useDeferredValue vs. useTransition: useTransition wraps the state update; useDeferredValue wraps the value consumption. Use useDeferredValue when you don't control the state update (e.g., it comes from a prop or parent).

FAQs

Why must the child component be wrapped in React.memo for useDeferredValue to help?
  • Without React.memo, the child re-renders on every parent render with the current value, not the deferred one.
  • React.memo skips the re-render when props haven't changed, allowing the deferred value to lag behind.
  • Without memoization, useDeferredValue provides no rendering benefit.
How do I detect that the deferred value is stale and show a loading indicator?
const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
 
return (
  <div className={isStale ? "opacity-50" : ""}>
    <Results query={deferredQuery} />
  </div>
);
  • Compare the deferred value to the current value to determine staleness.
What is the difference between useDeferredValue and debouncing?
  • Debouncing delays the state update itself, reducing the number of updates.
  • useDeferredValue lets the state update immediately but defers when the child re-renders.
  • Use debouncing for API calls; use useDeferredValue for render performance.
Gotcha: Why does passing a new object or array to useDeferredValue defeat its purpose?
  • useDeferredValue compares values with Object.is, which checks reference equality.
  • A new object {} or array [] created during render always has a new reference.
  • Memoize objects with useMemo before passing them to useDeferredValue.
Does useDeferredValue reduce the number of API calls?
  • No. It defers rendering, not the value change or fetch execution.
  • If you need fewer API calls, use debounce or throttle on the input handler.
  • useDeferredValue only helps with expensive React rendering.
How does useDeferredValue work with Suspense?
  • If the deferred value causes a child to suspend, React shows the old content instead of a fallback.
  • The background render is interruptible -- if the value changes again, React abandons the in-progress render.
  • This provides a smooth transition between old and new content.
What is the React 19 initialValue parameter?
// First render uses [] instead of the full items array
const deferredItems = useDeferredValue(items, []);
  • The second argument sets the deferred value for the initial render.
  • On subsequent renders, the deferred value tracks the real value at low priority.
Gotcha: Can I use useDeferredValue without Suspense?
  • Yes. useDeferredValue works with any expensive child component, not just Suspense-enabled ones.
  • Without Suspense, it simply defers the re-render of the child that receives the deferred value.
  • Suspense adds the benefit of showing old content while new content loads.
How do I type useDeferredValue in TypeScript?
// Type is inferred from the input value
const deferredQuery = useDeferredValue(query); // string
 
// React 19 with initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);
When should I choose useDeferredValue over useTransition?
  • Use useDeferredValue when you receive a fast-changing value as a prop and don't control the state update.
  • Use useTransition when you control the state setter and want to wrap the update explicitly.
  • Both achieve the same goal -- keeping the UI responsive during expensive renders.
  • useTransition — wrap state updates instead of deferring values
  • useMemo — memoize expensive computations
  • useCallback — stabilize references for memoized children
  • useState — the state source driving deferred values