useDeferredValue Hook
Defer updating a part of the UI to keep the rest responsive during expensive renders.
Search across all documentation pages
Defer updating a part of the UI to keep the rest responsive during expensive renders.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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.
"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 responsivedeferredQuery lags behind, so HeavyList re-renders at a lower priorityReact.memo on HeavyList is essential - without it, the component re-renders with query anywaydeferredQuery !== queryuseDeferredValue accepts a value and returns a deferred copy of it| 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 |
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>
);// Type is inferred from the input value
const deferredQuery = useDeferredValue(query); // string
// React 19 with initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);Forgetting React.memo - useDeferredValue 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 calls - useDeferredValue 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.
| 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).
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.useDeferredValue provides no rendering benefit.const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
return (
<div className={isStale ? "opacity-50" : ""}>
<Results query={deferredQuery} />
</div>
);useDeferredValue lets the state update immediately but defers when the child re-renders.useDeferredValue for render performance.useDeferredValue compares values with Object.is, which checks reference equality.{} or array [] created during render always has a new reference.useMemo before passing them to useDeferredValue.debounce or throttle on the input handler.useDeferredValue only helps with expensive React rendering.// First render uses [] instead of the full items array
const deferredItems = useDeferredValue(items, []);useDeferredValue works with any expensive child component, not just Suspense-enabled ones.// Type is inferred from the input value
const deferredQuery = useDeferredValue(query); // string
// React 19 with initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);useDeferredValue when you receive a fast-changing value as a prop and don't control the state update.useTransition when you control the state setter and want to wrap the update explicitly.Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥