⚛️ React Hook: useDeferredValue
📖 Quick Summary
useDeferredValuelets you render a lagging (deferred) version of a value so the UI can stay responsive during heavy re-renders.- It doesn’t schedule updates itself (unlike
useTransition); it returns a deferred copy you can render from. - Ideal when you can’t control where state updates happen but you want to defer parts of the UI that are expensive.
- Signature:tsx
const deferred = useDeferredValue(value, { timeoutMs? });
🧠 Mental Model
- Think of
useDeferredValueas a “lagging mirror” of some state. - The original value updates immediately (for input, cursor, etc.).
- The deferred value trails behind until React can render it without blocking the UI.
- While deferred, you can show a spinner/skeleton and keep the previous expensive UI visible.
🔑 Key Concepts
Lagging Copy
deferredstays equal to the previous value during busy rendering, then eventually “catches up”.
No Scheduling
- Unlike
useTransition, it doesn’t mark work as low priority. It simply defers the value used by part of the tree.
- Unlike
When to Use
- You need the latest input to feel instant, but rendering results can lag (e.g., search/filter results).
Pair with
memo- Use
React.memoon heavy child components that consume the deferred value so they only re-render when the deferred value changes.
- Use
timeoutMs(optional)- A hint for how long React may delay updating the deferred value before making it urgent (not a hard guarantee).
💻 Code Examples
Example 1: Responsive search input with deferred query
import { useDeferredValue, useMemo, useState } from "react";
function heavyFilter(items: string[], query: string) {
// simulate expensive work
const start = performance.now();
while (performance.now() - start < 5) {/* burn ~5ms */}
return items.filter(i => i.toLowerCase().includes(query.toLowerCase()));
}
export default function Search({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => heavyFilter(items, deferredQuery), [items, deferredQuery]);
const isStale = query !== deferredQuery;
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." />
{isStale && <p>Updating results…</p>}
<ul>
{results.map((r, i) => <li key={i}>{r}</li>)}
</ul>
</div>
);
}How it works (step‑by‑step):
- User types →
queryupdates immediately (keeps typing snappy). deferredQuerylags behind while React is busy.- Heavy list filtering uses
deferredQuery, so it only recomputes when the deferred value catches up. isStaleindicates that the UI is temporarily showing results for an older query; show a hint/spinner.- When React is free,
deferredQuerybecomesqueryandresultsupdate.
Example 2: Expensive child renders only when deferred value changes
import { memo, useDeferredValue, useMemo, useState } from "react";
const HeavyList = memo(function HeavyList({ items, query }: { items: string[], query: string }) {
// simulate heavy render
const start = performance.now();
while (performance.now() - start < 15) {/* 15ms work */}
const filtered = useMemo(
() => items.filter(i => i.toLowerCase().includes(query.toLowerCase())),
[items, query]
);
return <ul>{filtered.map((i, idx) => <li key={idx}>{i}</li>)}</ul>;
});
export default function Container({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<HeavyList items={items} query={deferredQuery} />
{query !== deferredQuery && <small>Rendering heavy list in background…</small>}
</div>
);
}How it works (step‑by‑step):
HeavyListis memoized; it re-renders only ifitemsorqueryprop changes.- Passing
deferredQuerymeansHeavyListdoesn’t re-render on every keystroke. - It re-renders only when the deferred value catches up → smoother typing.
Example 3: Comparing useTransition vs useDeferredValue
// useTransition: schedule non-urgent updates (you control setState timing)
const [isPending, startTransition] = useTransition();
const onChange = (next: string) => {
setQuery(next); // urgent
startTransition(() => setResults(expensiveCompute(next))); // non-urgent
};
// useDeferredValue: render from a lagging copy when you can't schedule
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => expensiveCompute(deferredQuery), [deferredQuery]);Takeaway:
- Use
useTransitionwhen you own the setState and want to mark it non‑urgent. - Use
useDeferredValuewhen you don’t control the updates (or want only part of the tree to lag).
Example 4: Optional timeout hint
import { useDeferredValue } from "react";
export default function DeferredWithTimeout({ value }: { value: string }) {
const deferred = useDeferredValue(value, { timeoutMs: 200 });
const isStale = deferred !== value;
return (
<p>
Current: {value} — Deferred: {deferred}
{isStale && " (catching up…)"}
</p>
);
}How it works (step‑by‑step):
- React may delay updating
deferredup to ~200ms under load (not guaranteed). - While stale, you can show lightweight progress UI.
⚠️ Common Pitfalls & Gotchas
- ❌ Expecting
useDeferredValueto schedule updates — it doesn’t; it just returns a deferred copy. - ❌ Using it for small/light components — adds complexity without benefit.
- ❌ Forgetting memoization (
memo/useMemo) in heavy children — they may still re-render for other prop changes. - ❌ Assuming
timeoutMsis exact — it’s a hint; React may update earlier or later.
✅ Best Practices
- Use for heavy subtrees fed by fast-changing inputs (search, filters, visualizations).
- Combine with
React.memoanduseMemofor best results. - Keep a simple staleness indicator:
value !== deferredValue. - Prefer
useTransitionwhen you can; reach foruseDeferredValuewhen you can’t control state updates or only want part of the UI to lag.
❓ Interview Q&A
Q1. What problem does useDeferredValue solve?
A: It keeps the UI responsive by letting you render from a lagging copy of a fast-changing value, so heavy parts don’t re-render on every change.
Q2. How is it different from useTransition?
A: useTransition schedules low-priority updates; useDeferredValue defers consumption of a value by parts of the UI without changing how it’s produced.
Q3. When would you prefer useDeferredValue?
A: When updates are driven elsewhere (e.g., third-party state, uncontrolled input) or you want only a subset of the UI to lag.
Q4. How do you detect that the deferred value is stale?
A: Compare value !== deferredValue to show a spinner/skeleton.
Q5. Does useDeferredValue reduce CPU cost by itself?
A: No; it spreads work over time. Optimize heavy computations with memoization, virtualization, or workers.