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:
queryupdates instantly on every keystroke, keeping the input responsivedeferredQuerylags behind, soHeavyListre-renders at a lower priorityReact.memoonHeavyListis essential — without it, the component re-renders withqueryanyway- Visual staleness feedback via opacity when
deferredQuery !== query
Deep Dive
How It Works
useDeferredValueaccepts 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
| Parameter | Type | Description |
|---|---|---|
value | T | The value you want to defer |
initialValue | T (React 19) | Optional initial value for the first render |
| Return | Type | Description |
|---|---|---|
deferredValue | T | The 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.memo —
useDeferredValueonly 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 inReact.memo. -
Not suitable for throttling API calls —
useDeferredValuedefers rendering, not the value change itself. It does not reduce the number of fetch calls. Fix: Usedebouncefor API calls; useuseDeferredValuefor 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.iscomparison finds a new value each time. Fix: Memoize objects or arrays withuseMemobefore passing touseDeferredValue.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
useTransition | You control when the state update happens and want to wrap it explicitly | You receive the value as a prop and cannot control when it changes |
| Debouncing | You want to reduce the number of state updates or API calls | You want instant feedback with deferred rendering |
| Virtualization | The bottleneck is rendering too many DOM nodes | The bottleneck is computation, not DOM count |
| Web Worker | Heavy computation should be moved off the main thread entirely | The 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.memoskips the re-render when props haven't changed, allowing the deferred value to lag behind.- Without memoization,
useDeferredValueprovides 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.
useDeferredValuelets the state update immediately but defers when the child re-renders.- Use debouncing for API calls; use
useDeferredValuefor render performance.
Gotcha: Why does passing a new object or array to useDeferredValue defeat its purpose?
useDeferredValuecompares values withObject.is, which checks reference equality.- A new object
{}or array[]created during render always has a new reference. - Memoize objects with
useMemobefore passing them touseDeferredValue.
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
debounceorthrottleon the input handler. useDeferredValueonly 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.
useDeferredValueworks 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
useDeferredValuewhen you receive a fast-changing value as a prop and don't control the state update. - Use
useTransitionwhen you control the state setter and want to wrap the update explicitly. - Both achieve the same goal -- keeping the UI responsive during expensive renders.
Related
- 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