Skip to content

useDeferredValue Patterns

advanced9 min read

The Value That Lags Behind

Think of useDeferredValue as the sibling of useTransition that you use when you don't own the state update. It takes a value and returns a "deferred" version that deliberately lags behind. During urgent renders, the deferred value keeps its old value. Once the urgent render completes, React schedules a background render with the new value.

function SearchResults({ query }) {
  // deferredQuery lags behind query during heavy renders
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  // Expensive filtering uses the deferred value
  const results = filterProducts(deferredQuery);

  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      {results.map(r => <ProductCard key={r.id} product={r} />)}
    </div>
  );
}

When the user types "abc", each keystroke updates query immediately. But deferredQuery stays at the old value until React has time to process the expensive filter. The input is responsive. The list updates when the browser is free.

The Mental Model

Mental Model

Think of useDeferredValue as a news ticker that runs one story behind. The newsroom (parent component) receives breaking stories in real-time. The ticker (deferred value) shows the previous story while the new one is being typeset. The audience sees a seamless flow — the ticker is always one story behind, but it never pauses or flickers.

When the newsroom is calm (no heavy renders), the ticker catches up immediately. When stories come in rapid succession (fast typing), the ticker stays behind, showing the latest fully-processed story.

useDeferredValue vs useTransition

People always ask "which one should I use?" Both achieve similar results. The difference comes down to who owns the state update:

// useTransition: YOU control the state update
function SearchPage() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value);  // Can split: sync for input, transition for results
    startTransition(() => {
      setFilteredQuery(value); // You decide what's deferred
    });
  }
}

// useDeferredValue: SOMEONE ELSE controls the state update
function SearchResults({ query }) {
  // You receive query as a prop — can't control how it was set
  const deferredQuery = useDeferredValue(query);
  // The deferral happens at the consumption point
}

Use useTransition when you own the state update (event handlers, actions). Use useDeferredValue when you receive the value as a prop and can't control how it was set.

Execution Trace
Type 'a'
query='a', deferredQuery=''
Fast render: query updated, deferredQuery still empty
Type 'ab'
query='ab', deferredQuery=''
Another fast render. deferredQuery hasn't caught up
Idle
query='ab', deferredQuery='ab'
Browser is free. React renders with deferredQuery='ab'
Type 'abc'
query='abc', deferredQuery='ab'
New keystroke. deferredQuery falls behind again
Idle
query='abc', deferredQuery='abc'
Catches up when browser is idle

Practical Patterns

Pattern 1: Deferred List Rendering

function FilterableList({ items, filter }) {
  const deferredFilter = useDeferredValue(filter);
  const isStale = filter !== deferredFilter;

  // Memoize the expensive rendering with the deferred value
  const filteredItems = useMemo(
    () => items.filter(item => item.name.includes(deferredFilter)),
    [items, deferredFilter]
  );

  return (
    <div style={{ opacity: isStale ? 0.6 : 1 }}>
      {filteredItems.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </div>
  );
}

The useMemo combined with deferredFilter means the expensive filtering only runs when the deferred value catches up — not on every keystroke.

Pattern 2: Deferred Chart Updates

function RealTimeChart({ dataPoints }) {
  // Data arrives at 60fps from a WebSocket
  // Chart can't render 60 times per second
  const deferredData = useDeferredValue(dataPoints);

  return <Chart data={deferredData} />;
}

The chart renders when the browser is idle, not on every data tick.

Pattern 3: Deferred Suspense

function ProfilePage({ userId }) {
  const deferredId = useDeferredValue(userId);

  return (
    <Suspense fallback={<ProfileSkeleton />}>
      <ProfileContent userId={deferredId} />
    </Suspense>
  );
}

When userId changes, deferredId keeps the old value momentarily. React shows the old user's profile (stale but complete) instead of the Suspense fallback. Once the new user's data is ready, the deferred value catches up and the profile updates.

Common Trap

useDeferredValue only helps when the deferred value is used by an expensive child component or computation. If the component using the deferred value is trivially cheap to render, the deferral adds overhead (two renders) with no perceptible benefit.

// WASTEFUL: Badge is trivially cheap
function NotificationBadge({ count }) {
  const deferredCount = useDeferredValue(count);  // Pointless
  return <span>{deferredCount}</span>;  // Renders in microseconds anyway
}

How useDeferredValue Works Internally

If you're curious what's happening under the hood, it's surprisingly straightforward. React treats useDeferredValue similarly to a self-triggered transition:

// Conceptual implementation (simplified)
function useDeferredValue(value) {
  const [deferredValue, setDeferredValue] = useState(value);

  useEffect(() => {
    // Schedule an update at transition priority
    startTransition(() => {
      setDeferredValue(value);
    });
  }, [value]);

  return deferredValue;
}

When value changes, React immediately returns the old deferredValue (fast render). Then it schedules a transition to update deferredValue to the new value (background render). If another value change arrives before the transition completes, the transition is interrupted and restarted.

The initial render behavior

On initial render (mount), useDeferredValue returns the same value as the input — no lag. The deferral only kicks in on subsequent updates. This means your component always shows correct data on first render, and only shows stale data during rapid updates.

// Initial render: deferredQuery === query (both are '')
// First keystroke: deferredQuery = '', query = 'a' (deferred lags)
const deferredQuery = useDeferredValue(query);

Production Scenario: The Collaborative Spreadsheet

A spreadsheet app receives cell updates via WebSocket from other collaborators. Each update triggers re-rendering affected cells:

function Spreadsheet({ cells }) {
  // cells change frequently from WebSocket updates
  const deferredCells = useDeferredValue(cells);
  const isStale = cells !== deferredCells;

  return (
    <div className={isStale ? 'updating' : ''}>
      <table>
        {deferredCells.map((row, i) => (
          <tr key={i}>
            {row.map((cell, j) => (
              <Cell key={`${i}-${j}`} value={cell} />
            ))}
          </tr>
        ))}
      </table>
    </div>
  );
}

Without deferral: every WebSocket update triggers a full table re-render. 10 updates per second means the UI freezes.

With deferral: the table uses the deferred cells, rendering at whatever rate the browser can handle. The user's own edits (local state) are immediate. Remote updates render when idle.

Common Mistakes

What developers doWhat they should do
Using useDeferredValue when you own the state update
useTransition gives you isPending and more control over which updates are deferred. useDeferredValue defers at the consumption point
Use useTransition when you control the setState call. useDeferredValue is for received values
Not combining useDeferredValue with useMemo
Without useMemo, the expensive computation runs on both the fast render (stale value) and the background render (new value). useMemo skips the computation when the deferred value hasn't changed
Memoize expensive computations that use the deferred value
Using useDeferredValue on trivially cheap renders
Deferral adds two render passes (fast with old value, then background with new). For cheap renders, this overhead exceeds any benefit
Only defer values that feed into expensive components or computations
Not showing a stale indicator when data is deferred
Users see outdated data during deferral. Without a visual cue, they might act on stale information
Check value !== deferredValue and dim or mark stale content

Challenge

Challenge: Choose the right tool

// For each scenario, decide: useTransition, useDeferredValue,
// or neither? Explain why.

// Scenario 1: A search input in your own component
function MySearch() {
  const [query, setQuery] = useState('');
  const results = expensiveFilter(data, query);
  return (
    <>
      <input onChange={e => setQuery(e.target.value)} />
      <ResultList items={results} />
    </>
  );
}

// Scenario 2: A chart component that receives data as props
function Chart({ data }) {
  // data changes frequently from parent
  return <SVGChart points=`{data}` />;
}

// Scenario 3: A button that toggles a boolean
function Toggle() {
  const [on, setOn] = useState(false);
  return <button onClick={() => setOn(!on)}>`{on ? 'ON' : 'OFF'}`</button>;
}
Show Answer

Scenario 1: useTransition

You own the setQuery call. Use startTransition to separate the input update (sync) from the filter update (deferred):

const [query, setQuery] = useState('');
const [filterQuery, setFilterQuery] = useState('');
const [isPending, startTransition] = useTransition();

function handleChange(e) {
  setQuery(e.target.value); // Sync: input responsive
  startTransition(() => setFilterQuery(e.target.value)); // Deferred: filter
}
const results = expensiveFilter(data, filterQuery);

Scenario 2: useDeferredValue

You don't own the data source — it arrives as props. Defer at the consumption point:

function Chart({ data }) {
  const deferredData = useDeferredValue(data);
  return <SVGChart points={deferredData} />;
}

Scenario 3: Neither

A boolean toggle is instantaneous. The render cost of showing/hiding one element is negligible. Adding transitions or deferral adds overhead with zero benefit.

Quiz

Quiz
When does useDeferredValue return a different value than its input?

Key Rules

Key Rules
  1. 1useDeferredValue creates a copy that lags behind during heavy renders. The old value stays while the new renders in the background.
  2. 2Use useDeferredValue when you receive a value as a prop and can't control the state update. Use useTransition when you own the setState.
  3. 3Combine useDeferredValue with useMemo to skip expensive computations during the fast render pass.
  4. 4Check value !== deferredValue to detect stale content and show a visual indicator (dimming, loading bar).
  5. 5On initial mount, useDeferredValue returns the input value directly — no lag on first render.
  6. 6Only use with expensive renders. For cheap components, deferral adds overhead (two render passes) with no benefit.