useDeferredValue Hook
useDeferredValue is a React 18 hook that lets you defer re-rendering a part of the UI when the data driving it changes. While useTransition lets you mark the update as non-urgent, useDeferredValue lets you mark the value as non-urgent — useful when you do not own the setter (e.g. the value comes from a prop or from a library you cannot modify).
Syntax
import { useDeferredValue } from 'react'
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query)
// deferredQuery lags behind query during transitions
// React renders the component with the current (urgent) query
// but passes deferredQuery (old value) to the slow subtree
return <ResultsList query={deferredQuery} />
}During an urgent update (like a keystroke), React renders the component twice:
First pass — with the old deferred value (stale but fast — already in the DOM)
Second pass — in the background with the new deferred value (slow, can be interrupted)
useDeferredValue vs useTransition
Aspect | useTransition | useDeferredValue |
|---|---|---|
What you control | The state update call | The value after the fact |
When to use | You own the setter (useState / dispatch) | Value comes from props or a third-party hook |
API | startTransition(() => setState(x)) | const deferred = useDeferredValue(value) |
isPending flag | Yes — from the hook itself | Derive it: value !== deferredValue |
Both solve | Keeping input responsive during slow renders | Same |
Rule of thumb: prefer useTransition when you own the state. Reach for useDeferredValue when you receive a value from outside — such as a URL search param, a prop from a parent, or a third-party hook.
Detecting the Pending State
useDeferredValue does not return an isPending flag directly. Instead, compare the live value with the deferred value:
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery // true while deferred render is pending
return (
<div>
{isStale && <p>Updating…</p>}
<ResultsList
query={deferredQuery}
style={{ opacity: isStale ? 0.6 : 1 }}
/>
</div>
)
}A Complete Search Example
The pattern that showcases useDeferredValue best is a search input whose results list is expensive to render. The input should respond instantly; the results list can lag slightly.
import { useState, useDeferredValue, memo } from 'react'
// memo is critical — without it React re-renders ResultsList even when
// deferredQuery hasn't changed yet, defeating the purpose
const ResultsList = memo(function ResultsList({ query }) {
const results = filterExpensiveData(query) // slow CPU work
return (
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
})
export default function SearchPage() {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search products…"
style={{ fontSize: 18, padding: '8px 12px', width: '100%' }}
/>
<p style={{ color: '#666', minHeight: 24 }}>
{isStale ? 'Filtering…' : `${query ? 'Results for' : 'All'} items`}
</p>
<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
<ResultsList query={deferredQuery} />
</div>
</div>
)
}Combining useDeferredValue with Suspense
useDeferredValue pairs naturally with Suspense. When the deferred value changes and triggers a suspended component, React keeps showing the old (committed) content instead of the nearest Suspense fallback — the UI stays stable until the new data is ready.
import { Suspense, useDeferredValue } from 'react'
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
return (
<div style={{ opacity: isStale ? 0.6 : 1 }}>
{/* Suspense fallback only shows on FIRST load.
On subsequent updates, the old content stays visible
while the new data loads — thanks to useDeferredValue. */}
<Suspense fallback={<p>Loading initial results…</p>}>
<AsyncResultsList query={deferredQuery} />
</Suspense>
</div>
)
}
// Inside AsyncResultsList, use() or a Suspense-compatible data library
// suspends while the new query's data is fetchingHow React Schedules the Deferred Re-render
Internally, useDeferredValue works by scheduling a low-priority render after the high-priority (urgent) render commits. React uses its concurrent scheduler to determine when to yield back to the browser for paint and input handling. This means:
On fast machines the deferred render may happen almost immediately after the urgent one
On slow machines (or when the deferred component is very expensive) there may be a visible gap
If another urgent update arrives before the deferred render finishes, React abandons the in-progress deferred render and restarts it with the latest value
The deferred render is interruptible — it never blocks user interaction
When NOT to Use useDeferredValue
The component is already fast — adding useDeferredValue to cheap renders adds complexity for no gain
You need the deferred value to be exactly up to date — by design it lags behind; don't use it for calculations where stale data is dangerous
You own the setter — use useTransition instead for a cleaner API and a first-class isPending flag
Debouncing is sufficient — if you just want to delay an API call, a simple debounce with useEffect is simpler and more predictable
useDeferredValue as a Debounce Alternative
A common question: is useDeferredValue a replacement for debouncing? Not exactly. Debouncing waits a fixed time before updating; deferred values wait for the browser to have idle time — it is adaptive. On a fast machine you get near-instant updates; on a slow machine React automatically gives more breathing room.
// Classic debounce approach (fixed 300ms delay)
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(id)
}, [value, delay])
return debounced
}
// useDeferredValue approach (adaptive, no fixed delay)
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query)
// Works better on both fast and slow devices
}Key Takeaways
useDeferredValue(value)returns a version of value that lags behind during concurrent rendersWrap the slow-to-render subtree in React.memo so it can skip re-rendering while the deferred value is still stale
Compare
value !== deferredValueto derive an isPending flagWorks with Suspense: keeps old content visible instead of showing the fallback on updates
Prefer
useTransitionwhen you own the state setter; useuseDeferredValuewhen you receive the value from outside