useDeferredValue: Deferring Renders Without Blocking Input
useDeferredValue gives you a stale copy of a value that React can update lazily. It is the consumer-side equivalent of useTransition — useful when you cannot wrap the state update itself but still need to deprioritize an expensive downstream render.
useDeferredValue solves the same core problem as useTransition — keeping the UI responsive while expensive renders happen. The difference is where control sits.
With useTransition, you wrap the state update itself in startTransition. That works when you own the setter. But sometimes you receive a value from props, context, or a parent component — you cannot wrap its update. useDeferredValue handles that case by deferring at the consumption point instead.
How It Works
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);query updates immediately on every keystroke. deferredQuery is a deferred copy — React updates it when the browser has idle capacity. Any component or computation that reads from deferredQuery re-renders lazily, after urgent work is done.
Practical Example: Deferred Filtering
function SearchList({ items }) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const filteredItems = items.filter(item =>
item.includes(deferredQuery)
);
return (
<>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</>
);
}The input reads from query — it responds instantly. The filtered list reads from deferredQuery — it updates when React has time. Typing fast does not cause the input to lag because the expensive filtering is decoupled from the keystroke update.
Detecting When the Value Is Stale
Compare query to deferredQuery to know when a deferred render is still in progress:
const isStale = query !== deferredQuery;
{isStale && <p>Loading...</p>}When query has moved ahead of deferredQuery, the filtered list is still catching up. That gap is your loading window. Show a spinner, fade the results, or display any indicator that something is happening.
useTransition vs useDeferredValue
Both hooks exist to keep interactions responsive during expensive renders. The choice depends on where you have control.
Use useTransition when you own the state update — you can wrap the setter call in startTransition directly at the call site.
Use useDeferredValue when you receive the value from outside — props, context, a parent — and cannot control when the state is set. You defer the value at the point where you consume it.
A quick comparison:
useTransitionwraps the update. You getisPendingdirectly.useDeferredValuewraps the value. You derive pending state by comparingvalue !== deferredValue.useTransitionrequires explicitly marking the slow work withstartTransition.useDeferredValueautomatically defers without any wrapping at the update site.
Think of It as a Built-in Debounce
The closest analogy is a debounce — delaying a value update until the user stops typing. The key difference: useDeferredValue does not use a fixed time delay. React decides when to update the deferred value based on how busy the main thread is. If the user types slowly, deferredQuery catches up almost instantly. If they type quickly, React batches the updates and renders only the most recent value when it gets a chance.
No timer management, no arbitrary delay constant, no teardown logic. React handles the scheduling.
Practice what you just read.
Keep reading