useDeferredValue

Reviewed & published by Brayan K

useDeferredValue is a React Hook that returns a deferred copy of a value, allowing an expensive part of your UI to lag a moment behind a fast-changing input so the input stays responsive while heavy results catch up. You give it a value and React gives you back a version that updates at a lower priority.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ The Idea: A Value That Catches Up

Call const deferred = useDeferredValue(value). The live value updates immediately (great for the input), while deferred may briefly hold the previous value during a busy render, then catch up. Pass deferred — not value — to the heavy part.

function Search() {
  const [text, setText] = useState("");
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <SlowResults query={deferredText} />    
    </>
  );
}
// useDeferredValue lets an EXPENSIVE child lag behind a FAST input.
// We can't run the hook here, so we model the two values it gives you:
// the live value (updates now) and the deferred value (catches up later).

let liveValue = "";
let deferredValue = "";

function type(char) {
  liveValue += char;
  console.log("input shows:", liveValue);        // instant
  // React lets the deferred value lag during a busy render…
}

function catchUp() {
  deferredValue = liveValue;                       // …then it catches up
  console.log("results use:", deferredValue);
}

type("r"); type("e"); type("a"); type("c"); type("t");
catchUp();
// input shows: r / re / rea / reac / react
// results use: react

2️⃣ Pair It With React.memo

Deferring only helps if the expensive child can skip re-rendering when its prop hasn't changed. Wrap that child in React.memo and feed it the deferred value. Now it re-renders only when the deferred value updates — not on every keystroke — which is where the smoothness comes from.

const SlowResults = React.memo(function SlowResults({ query }) {
  // heavy filtering/rendering here
  const matches = bigList.filter((x) => x.includes(query));
  return <ul>{matches.map((m) => <li key={m}>{m}</li>)}</ul>;
});
// Because of memo, SlowResults only re-runs when "query"
// (the deferred value) actually changes.
// Detect stale results: if live value and deferred value differ,
// a fresher render is still pending — dim the list to show that.

function isStale(value, deferredValue) {
  return value !== deferredValue;
}

console.log(isStale("reac", "rea"));     // true  -> still catching up
console.log(isStale("react", "react"));  // false -> up to date

// In JSX you'd write:
//   <div style={{ opacity: isStale(value, deferred) ? 0.5 : 1 }}>

3️⃣ Showing Staleness & vs useTransition

When value !== deferredValue, a newer render is pending — dim the list to signal "catching up." The key difference from useTransition: a transition wraps the code that triggers the update (you own the setState), while useDeferredValue wraps a value you already have — perfect when the value is a prop you can't control.

const deferred = useDeferredValue(text);
const isStale = text !== deferred;
return (
  <div style={{ opacity: isStale ? 0.5 : 1 }}>
    <SlowResults query={deferred} />
  </div>
);
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// 1) staleness is true when value and deferred DON'T match.
// 2) opacity should be 0.5 when stale, else 1.

function isStale(value, deferred) {
  return value ___ deferred;            // 👉 use !== (not equal)
}

function listOpacity(stale) {
  return stale ___ 0.5 ___ 1;            // 👉 ternary: stale ? 0.5 : 1
}

console.log(isStale("reac", "rea"));     // true
console.log(isStale("go", "go"));        // false
console.log(listOpacity(true));          // 0.5
console.log(listOpacity(false));         // 1

// ✅ Expected output:
//    true
//    false
//    0.5
//    1

🧩 Reorder Challenge

These lines build a smooth search box. Put them in the right order:

A.   const deferredText = useDeferredValue(text);
B.   const [text, setText] = useState("");
C.   const isStale = text !== deferredText;
D.   <input value={text} onChange={(e) => setText(e.target.value)} />
E.   <SlowResults query={deferredText} />

Common Errors (and the fix)

📋 Quick Reference

NeedCode
Defer a valueconst d = useDeferredValue(text)
Feed the heavy child<Slow query={d} />
Skip re-rendersReact.memo(Slow)
Detect staletext !== d

🧠 Quick Recall

1. Which value do you pass to the expensive child?

The deferred value. The live value goes to the input; the lagging deferred value goes to the heavy results.

2. Why does it need React.memo?

So the child skips re-rendering while the deferred prop is unchanged. Without memo it re-renders every keystroke and the lag buys you nothing.

3. useTransition vs useDeferredValue — pick one in a sentence.

Use a transition when you own the setState; use a deferred value when you only have a value (often a prop) flowing in.

Mini-Challenge: Memo-Friendly Filter

Model an expensive filter that only recomputes when the query actually changes, the way a deferred value plus memo behaves. Run it and check your output.

// 🎯 MINI-CHALLENGE: a memo-friendly filter
//
// Expensive children should only recompute when the DEFERRED value
// changes. Write filterFor(query) that returns the matching items,
// and a render counter that only increments when the query is new.
//
// ✅ Expected output:
//    matches for "ho": hook,host
//    renders: 1
//    matches for "ho": hook,host
//    renders: 1   (same deferred value -> no extra work)

const items = ["hook", "host", "react", "state"];
let lastQuery = null;
let renders = 0;

// your code here

🎉 Lesson Complete

Practice quiz

What does useDeferredValue return?

  • A debounced callback
  • A memoized component
  • A deferred copy of a value that updates at a lower priority, lagging behind during busy renders
  • A boolean indicating loading

Answer: A deferred copy of a value that updates at a lower priority, lagging behind during busy renders. You pass it a value and get back a version that may briefly hold the previous value during a busy render, then catch up.

Which value do you pass to the expensive child component?

  • The deferred value
  • The live value
  • Both, alternating
  • Neither; you pass a ref

Answer: The deferred value. The live value goes to the fast input; the lagging deferred value goes to the heavy results so they update a beat later.

Why does useDeferredValue pair with React.memo?

  • memo is required by the hook
  • memo makes the value update faster
  • It prevents memory leaks
  • So the expensive child can skip re-rendering when its deferred prop is unchanged instead of re-rendering every keystroke

Answer: So the expensive child can skip re-rendering when its deferred prop is unchanged instead of re-rendering every keystroke. Deferring only helps if the child can skip work. Wrapping it in React.memo means it re-renders only when the deferred value actually changes.

How do you detect that results are stale (a newer render is pending)?

  • Check a loading boolean
  • Compare the live and deferred values: value !== deferredValue means it's catching up
  • Use a setTimeout
  • Read a ref count

Answer: Compare the live and deferred values: value !== deferredValue means it's catching up. When the live value differs from the deferred one, a fresher render hasn't landed yet — a common pattern is dimming the list while they differ.

Does useDeferredValue debounce with a fixed delay?

  • No — it's not a timer; the lag adapts to how busy React is
  • Yes, 300ms by default
  • Yes, but configurable
  • Only in production

Answer: No — it's not a timer; the lag adapts to how busy React is. It lets the deferred value lag during a busy render and catch up when React has spare time. For a fixed wait you'd still use a debounce.

How does useDeferredValue differ from useTransition?

  • They are identical
  • useTransition only works on the server
  • A transition wraps the code that triggers the update (you own the setState); useDeferredValue wraps a value you already have
  • useDeferredValue requires Suspense

Answer: A transition wraps the code that triggers the update (you own the setState); useDeferredValue wraps a value you already have. Use a transition when you own the setState. Use a deferred value when you only have a value flowing in — often a prop you can't control.

What's the bug if you pass the live value (not the deferred one) to the heavy child?

  • Nothing changes
  • Nothing is deferred — the expensive child still updates on every keystroke
  • The input stops working
  • React throws an error

Answer: Nothing is deferred — the expensive child still updates on every keystroke. Deferring requires feeding the deferred value to the expensive component; passing the live value defeats the entire optimization.

How would you dim stale results in JSX?

  • <div hidden={isStale}>
  • <div disabled>
  • <div className="loading">
  • <div style={{ opacity: isStale ? 0.5 : 1 }}>

Answer: <div style={{ opacity: isStale ? 0.5 : 1 }}>. A common pattern sets opacity to 0.5 while value !== deferred, visually signaling that fresher results are on the way.

When is useDeferredValue a better fit than useTransition?

  • When you want a fixed delay
  • When the slow input is a value (often a prop) you can't wrap the setState for
  • When you have no expensive child
  • When rendering on the server

Answer: When the slow input is a value (often a prop) you can't wrap the setState for. If you only have a value flowing in and can't control where its setState happens, deferring that value is the right tool.

Besides React.memo, what helps avoid recomputing an expensive filter each render?

  • useCallback on the child
  • useRef for the result
  • useMemo keyed on the deferred value
  • Nothing is needed

Answer: useMemo keyed on the deferred value. Wrapping the expensive computation in useMemo keyed on the deferred value avoids recomputing it on unrelated renders.

Continue this course

Frequently asked questions

What is the difference between useDeferredValue and useTransition?

useTransition wraps the code that triggers the update — you control where the slow state change happens. useDeferredValue wraps a value you already have (often a prop you can't change). Use a transition when you own the setState; use a deferred value when you only have a value flowing in.

Does useDeferredValue debounce?

Not exactly. It doesn't wait a fixed time. It lets the deferred value lag behind during a busy render and catch up when React has spare time, so the fast part (the input) updates now and the expensive part updates a beat later. The lag adapts to how slow the render is.

Why do I need React.memo with useDeferredValue?

Deferring only helps if the expensive child can skip re-rendering when its prop is unchanged. Wrap that child in React.memo and pass it the deferred value, so it only re-renders when the deferred value actually updates — not on every keystroke.

How do I show that results are stale?

Compare the live value with the deferred one: if they differ, a newer render is pending. Dim the list with an opacity style while value !== deferredValue to signal that fresher results are coming.