Skip to main content

What does useDeferredValue() do

What useDeferredValue() does

useDeferredValue(value) returns a "deferred version" of a value. If value changes often (for example, while typing in an input), React may not immediately propagate the new value into heavy parts of the UI (lists, charts), so the interface stays responsive. The input reacts instantly, while the "expensive" render catches up a little later.

A mini example:

javascript
const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // a "slow" copy // The heavy list filters by deferredQuery, not by the instant query const filtered = useMemo(() => heavyFilter(items, deferredQuery), [items, deferredQuery]);

While the user types, query changes right away (the input does not lag), while deferredQuery updates a little later - the heavy render does not "jerk" on every character.


How useDeferredValue() is similar to debounce

Both techniques smooth out frequent changes, so heavy work does not get triggered too often.

The similarity:

  • They lower the frequency of "expensive" recalculations/renders on fast input.
  • The user sees a smooth UI with no lag.

How useDeferredValue() differs from debounce

The main differences are in level and semantics:

CriterionuseDeferredValue()Debounce (for example, debounce(fn, 300))
Where it worksInside React, understands priorities, integrated with Concurrent RenderingOutside React (a timing utility), "blindly" delays calls
What it defersPropagating a value to consumers; the render can be interrupted/resumedCalling a function on a timer after a pause
Delay controlDynamic, no hard timer; React decides itself when to "catch up"A fixed interval (for example, 300 ms)
Cancellation/interruptionYes: the render can be interrupted if a more important update arrivesYes: the timer resets on new input, but with no knowledge of React's priorities
SSR/hydration/concurrencyNaturally compatibleNot integrated at all
Typical use casesDecoupling "fast" input from "heavy" UINetwork/handlers, throttling call frequency, autocomplete APIs

In short: debounce is about time, useDeferredValue is about render priority.


How useDeferredValue() differs from startTransition()

Both mechanisms serve "non-urgent" updates, but they apply at different points:

CriterionuseDeferredValue(value)startTransition(() => setState(...))
LevelA value: gives back a "slow" copy of a frequently changing valueAn operation/update: marks specific setState calls as low priority
Who initiates itThe consumer of the value chooses the "slow" versionYou yourself mark a piece of updates as a transition
Status controlNo built-in isPendingYou get isPending via useTransition()
GranularityPassive: any place that uses the deferred value will "catch up" laterActive: you explicitly wrap "heavy" updates in a transition
Typical use casesHeavy memoized computations/lists that depend on a fast-changing valueNavigation, filtering, requests, large-scale tree rebuilds
Asynchrony (React 19+)Not about asyncstartTransition can be await-ed (waiting for async work)

In short:

  • useDeferredValue - "give me a version of this value that can update later".
  • startTransition - "make these state updates low priority".

When to choose which

  • A heavy list/chart reacts to fast input → start with useDeferredValue(query). Simple and almost no code.
  • You need to mark a batch of updates as "non-urgent" and show an "updating" status → const [isPending, startTransition] = useTransition(); startTransition(() => setState(...)).
  • Limit the frequency of network calls / input handlers → debounce (network layer/utilities). You can combine them: debounce for the request, useDeferredValue for the heavy UI.

Common patterns

Combo "input + deferred filtering":

javascript
const [query, setQuery] = useState(''); const deferred = useDeferredValue(query); const list = useMemo(() => heavyFilter(items, deferred), [items, deferred]);

Mark a rebuild as a transition (with an indicator):

javascript
const [isPending, startTransition] = useTransition(); const onTabChange = (tab: string) => { startTransition(() => setActiveTab(tab)); // a large rebuild of the children }; return <> <Tabs onChange={onTabChange} /> {isPending && <Skeleton />} <HeavyPane tab={activeTab} /> </>;

Debounce only for the request:

javascript
const debouncedFetch = useMemo(() => debounce(fetchUsers, 300), []); useEffect(() => { debouncedFetch(query); }, [query, debouncedFetch]);

(and in parallel you can use useDeferredValue(query) for a "smooth" UI)


Summary

  • useDeferredValue - a deferred version of a value. About priority, not a timer.
  • Debounce - a timer: it defers function calls after a pause.
  • startTransition - marking state updates as low priority, with isPending and (in React 19+) await.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.