ReactuseDeferredValue Hook

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

JSX
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)

Note
The initial render always uses the value you passed — there is no "old" value yet. Deferral only kicks in on subsequent updates.
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:

JSX
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.

JSX
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>
  )
}
Warning
React.memo is not optional here — it is what makes the optimisation work. Without memo, React re-renders ResultsList on every keystroke anyway (because its parent re-renders), and useDeferredValue has no opportunity to skip the work.
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.

JSX
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 fetching
How 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.

JSX
// 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
}
Tip
For API calls you still want debouncing — you do not want to fire a network request on every single keystroke. Use debouncing for network work and useDeferredValue for CPU-bound rendering work. They are complementary.
Key Takeaways
  • useDeferredValue(value) returns a version of value that lags behind during concurrent renders

  • Wrap the slow-to-render subtree in React.memo so it can skip re-rendering while the deferred value is still stale

  • Compare value !== deferredValue to derive an isPending flag

  • Works with Suspense: keeps old content visible instead of showing the fallback on updates

  • Prefer useTransition when you own the state setter; use useDeferredValue when you receive the value from outside