C.W.K.
Stream
Lesson 02 of 06 · published

useDeferredValue — Lag the Heavy Render

~11 min · usedeferredvalue, concurrent, rendering

Level 0React Novice
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
useDeferredValue takes a value and gives you a 'late' version. The input stays urgent. The expensive render reads the late version and lags slightly. The result: typing feels instant; results catch up.

The shape

const deferred = useDeferredValue(value). deferred equals value initially, then equals the previous value during high-priority renders (typing) while the new render with the new value is in progress in the background. When the background render finishes, deferred updates and the heavy subtree re-renders with the new value.

useDeferredValue vs useTransition

Both achieve similar UX. The difference is who owns the value:

  • useTransition — you own the state, you wrap the update in startTransition.
  • useDeferredValue — the value comes from somewhere you don't control (a prop, a context). You can't wrap the update, but you can mark the value as deferrable.

Inside a component you control: useTransition. Receiving a prop: useDeferredValue.

Pairing with React.memo

useDeferredValue only helps if the expensive child can skip re-rendering when its props haven't changed. Wrap the child in React.memo (or trust the Compiler to memoize it). Without memoization, the deferred value still triggers a re-render every time, and you lose the benefit.

Defer the renders, not the data. The data is already there; you're choosing when React paints it. useDeferredValue says 'render the input update now, get to the expensive subtree when you can.' It's scheduling, not throttling.

Code

useDeferredValue with a memoized heavy child·tsx
import { memo, useDeferredValue, useState } from "react";

function SearchPage() {
  const [query, setQuery] = useState("");
  // Deferred lags behind query — typing stays instant.
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <div style={{ opacity: isStale ? 0.6 : 1 }}>
        <Results query={deferredQuery} />
      </div>
    </div>
  );
}

const Results = memo(function Results({ query }: { query: string }) {
  // Expensive render — memo skips it when query hasn't changed.
  // With useDeferredValue, this re-renders less often than the input.
  return <ul>{filterLargeList(query).map((r) => <li key={r}>{r}</li>)}</ul>;
});

function filterLargeList(_q: string): string[] { return []; }

External links

Exercise

Refactor the search exercise from lesson 1 to use useDeferredValue instead of useTransition. Wrap the results component in React.memo. Confirm the input stays responsive and the results lag slightly behind. Then remove the memo and observe that useDeferredValue alone doesn't help — both the input update and the expensive render fire on every keystroke.
Hint
memo + useDeferredValue is the canonical pair. Without memo, the deferred value still gets a fresh prop on every render, defeating the purpose.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.