cf.completefrontend
Open lab My progress
THE REACT FIELD GUIDE

Deferred values

Keep typing fast with React useDeferredValue: defer a slow list, keep old results while new ones load, use initialValue, and compare it with debouncing.

By the end, you can
  • 01
    Keep a text box fast next to a slow listPass a deferred value to the slow part, wrap it in memo, and explain the two renders React does for each key.
  • 02
    Show old content while new content loadsKeep old search results on screen instead of a Suspense fallback, dim them with value !== deferredValue, and give the first render an initialValue.
  • 03
    Pick the right toolTell deferring from debouncing, and choose between useDeferredValue and useTransition by whether you have the setter or only the value.

A value that is allowed to lag behind

Some parts of a page must answer at once, like the text box you type in. Other parts can catch up a moment later, like a long preview of what you typed. useDeferredValue is a Hook from React that gives you a second, lagging copy of a value. You give the urgent part the real value and the slow part the copy. Here a greeting card editor shows both, with nothing slow yet. The result is live: click in the box and type H, then open the console under it.

A message and its deferred copyPop out in the code editor (opens in a new tab)JSX
import { useDeferredValue, useState } from "react"; export default function CardEditor() {  const [message, setMessage] = useState("");  const deferredMessage = useDeferredValue(message);  console.log(`message: "${message}", deferredMessage: "${deferredMessage}"`);  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <p>Box: {message}</p>      <p>Preview: {deferredMessage}</p>    </>  );}
ResultLoading…

Line 4 keeps the message in state. Line 5 passes it to useDeferredValue and gets back deferredMessage. Line 6 logs both on every render. Line 10 shows the real value and line 11 the deferred one.

When you type H, the console shows two renders:

  1. message: "H", deferredMessage: "". React renders at once with the new message, but useDeferredValue still returns the old value. This render is urgent: it puts the key in the box.
  2. message: "H", deferredMessage: "H". Right after, React renders again in the background, with the deferred value caught up.

Each line has a small 2 beside it: in development, StrictMode renders every component twice to help you find mistakes, so each render logs twice. Both renders are so quick here that the two lines on the screen seem to change together. The next sections make the second render slow, and that's where the lag starts to help.

One key press, two renders. The reader types H. Render 1 has message "H" and deferredMessage "", and the box updates. Render 2, in the background, has message "H" and deferredMessage "H", and the preview catches up.type Hrender 1message: "H"deferred: ""render 2message: "H"deferred: "H"urgent: the box updatesbackground: the preview catches up
Typing H makes React render twice. Render 1 is urgent: message is new, deferredMessage is still the old value. Render 2 runs in the background with both new.
Real-life analogyA whiteboard and a sign painter

A shop owner plans a sign. She writes the words on a whiteboard, and a painter copies them onto the big sign outside. She never waits for the painter to write the next word. When she changes the words, the painter wipes the half-painted letters and starts again from the whiteboard. People outside see the old sign until the new one is done.

In real life: You write your message on a whiteboard, and it's there at once
In React: message updates the box on every key
In real life: A painter copies the whiteboard onto a big sign, which takes a while
In React: React renders the slow part with deferredMessage in the background
In real life: You change the whiteboard mid-painting, and the painter starts over from the new text
In React: A new key throws the unfinished background render away
In real life: The old sign stays up until the new one is finished
In React: The screen keeps the old version of the slow part until the new render is done
The main idea

useDeferredValue(value) returns a copy of value that lags behind it. When the value changes, React first renders with the old copy, so the urgent parts update at once. Then it renders again in the background with the new copy. A newer change interrupts that background render.

A slow list makes typing lag

Now the card editor previews your message on 100 card designs. Each design in the CardDesigns.jsx tab is slow on purpose: lines 5 to 8 of that tab wait 1 millisecond in a loop before it returns, like a design with a lot to draw. 100 designs take about 0.1 seconds, and twice that in development, where StrictMode renders them twice. Click in the box and type Happy birthday quickly:

Every key redraws 100 slow designsPop out in the code editor (opens in a new tab)JSX
import { useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("");  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={message} />    </>  );}
ResultLoading…

The letters don't keep up with your fingers. They appear in jumps, a few at a time. Line 9 passes message straight to CardDesigns, so every key makes React render all 100 designs before the box can show the new letter. The console shows Drawing 100 designs for "H", "Ha", "Hap", and so on (from line 14 of the CardDesigns.jsx tab), one for every key.

Why can't the box show the letter first? JavaScript does one thing at a time. While React renders the designs, the browser can't paint the screen or handle the next key; it waits until that piece of JavaScript ends. The event loop lesson shows that line of waiting tasks, and Rendering and the event loop shows where painting fits in it. A plain state update renders in one piece, so the whole 0.2 seconds stands between your key and the screen.

Defer the slow part, keep the box urgent

The fix has two parts. Give the designs a deferred copy of the message, and let React skip the designs when that copy hasn't changed:

The designs read a deferred copy of the messagePop out in the code editor (opens in a new tab)JSX
import { useDeferredValue, useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("");  const deferredMessage = useDeferredValue(message);  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={deferredMessage} />    </>  );}
ResultLoading…

Line 6 makes deferredMessage, and line 10 passes it to CardDesigns. The box on line 9 still uses message. The second part is in the CardDesigns.jsx tab: line 22 now exports memo(CardDesigns) instead of CardDesigns. memo is a function from React that wraps a component. When its parent renders again, React compares each new prop with the old one, using Object.is, and skips the component if none of them changed.

Type Happy birthday quickly again. Each key now goes through two renders:

  1. The urgent render has the new message and the old deferredMessage. CardDesigns gets the same message as last time, so memo skips it. The render takes almost no time, and the box shows your key.
  2. The background render has the new deferredMessage, so CardDesigns draws all 100 designs. React works on it in small slices of about 5 milliseconds and checks for keys between them. When you press another key, React throws this render away, handles the key, and starts again with the newest message.

The box keeps up with you, and the designs catch up when you pause. The console shows it: when you type fast, you'll see Drawing 100 designs for messages that never appeared on the screen. React started them and dropped them for a newer key.

The background render has the same low priority as a transition, which is why a key can interrupt it. Later in the course, Lanes explains those priorities, and Concurrent rendering, Suspense and transitions inside reads the code in React that schedules this second render.

Three keys typed quickly: H, He, Hey. Each one shows in the box at once. React starts drawing the designs for H, then for He, and throws both drawings away when the next key comes. Only the drawing for Hey finishes and reaches the screen.boxdesigns"H"draw "H"thrown away"He"draw "He"thrown away"Hey"draw "Hey"on screenEach new key stops the unfinished drawing.
Each key shows in the box at once. React starts the 100 designs in the background, but the next key comes before they're done, so it throws that drawing away and starts again. Only the drawing for "Hey" reaches the screen.

useDeferredValue doesn't make the designs any faster. Each drawing takes just as long. It changes when they draw: after the box, and only for the newest message. In the playground, the same editor runs on this page. Choose a version, then type, or press the button to type Happy birthday at a steady speed. The list of waits shows how long each key waited, and which drawings reached the screen.

Playground: how long does each key wait?
The card editorplain

Type a message to see the designs.

Each key's waitno keys yet
    Try it yourself
    Plain state

    Choose a version, then type in the box, or press the button to type “Happy birthday” at a steady speed. Each key's wait appears in the list of waits.

    The card editor runs here, on this page, with React's real useDeferredValue and the same 100 slow designs as the CardDesigns.jsx tab. A key's wait runs from the moment you pressed it to the moment React put it in the box. The button presses one key every 60 ms, with a longer pause after “Happy”. Changing the version starts over with an empty box.

    With Plain state, the waits grow as you type, because every key queues behind a full drawing. With useDeferredValue, every key shows in a few milliseconds, and most drawings say thrown away. The third version, Debounced 300 ms, comes up later in this lesson.

    Both parts matter. Without memo, the urgent render would draw all 100 designs again for the old message, and the box would wait for that. Exercise 3 lets you see it in the console. memo has its own lesson, memo, useMemo and useCallback. The React Compiler applies the same skipping to your components for you, so with it you don't write memo by hand.

    Keep old results while new ones load

    Deferred values help with waiting for data too. In Suspense, a component that reads a promise with use suspends until the promise resolves, and the nearest boundary shows its fallback. Here is a product search built that way. The data.js tab is a pretend server that answers each search after 0.8 seconds and keeps every answer in a cache. Wait for the six products, then type c, wait, and type a:

    A search where every key shows the fallbackPop out in the code editor (opens in a new tab)JSX
    import { Suspense, useState } from "react";import SearchResults from "./SearchResults.jsx"; export default function ProductSearch() {  const [query, setQuery] = useState("");  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search products" />      <Suspense fallback={<p>Loading…</p>}>        <SearchResults query={query} />      </Suspense>    </>  );}
    ResultLoading…

    Line 10 passes query to SearchResults, which reads use(fetchResults(query)) on line 5 of its tab. Every new query is a new search, so the results suspend, and the boundary on lines 9 to 11 swaps them for Loading… for 0.8 seconds. The list blinks away on every key. Now give the results the deferred query:

    Old results stay, dimmed, while new ones loadPop out in the code editor (opens in a new tab)JSX
    import { Suspense, useDeferredValue, useState } from "react";import SearchResults from "./SearchResults.jsx"; export default function ProductSearch() {  const [query, setQuery] = useState("");  const deferredQuery = useDeferredValue(query);  const isStale = query !== deferredQuery;  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search products" />      <Suspense fallback={<p>Loading…</p>}>        <div style={{ opacity: isStale ? 0.5 : 1 }}>          <SearchResults query={deferredQuery} />        </div>      </Suspense>    </>  );}
    ResultLoading…

    Line 6 makes deferredQuery and line 13 passes it to the results. Type c and wait, then type a. The box says ca at once, and the results for c stay on screen, faded, until the results for ca arrive 0.8 seconds later. Here's why:

    1. The urgent render has query set to "ca" and deferredQuery still "c". Those results are in the cache, so nothing suspends, and the box updates.
    2. The background render asks for "ca", which suspends. A background render that suspends isn't shown. React keeps the screen as it is and tries again when the promise resolves.

    Line 7 compares the two values. While they differ, isStale is true, and line 12 fades the old results to half opacity, so the reader can tell they belong to an older query. A transition does the same for content that's already showing, as Transitions showed. The very first load still shows Loading…: there are no old results to keep yet.

    Three moments of the product search. First, the box says c and the results for c show: Black tea, Lemon cake, Carrot cake. Then the reader types ca: the box says ca at once, and the old results stay, dimmed. 0.8 seconds later the results for ca show: Lemon cake and Carrot cake.ccBlack teaLemon cakeCarrot cakecacaBlack teaLemon cakeCarrot cake0.8 scaLemon cakeCarrot cakeresults for "c"old results, dimmedresults for "ca"
    The box always shows what you typed. While the results for "ca" load, the results for "c" stay on screen, dimmed, instead of a fallback.

    Look at the console after typing ca: it shows Search "c" and Search "ca", from line 14 of data.js. Deferring doesn't send fewer requests. Every query you type still renders, and every new query still starts its search. What it changes is what the reader sees while they load.

    A quick first render with initialValue

    On the first render there's no old value to show, so by default useDeferredValue returns the value itself, and the slow part renders before anything appears. React 19 added a second argument, initialValue, for that first render. The React 19 release post describes it. Here the editor opens with a saved draft:

    The first render uses the initial valuePop out in the code editor (opens in a new tab)JSX
    import { useDeferredValue, useEffect, useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("Happy birthday, Asha!");  const deferredMessage = useDeferredValue(message, "");  useEffect(() => {    console.log(`On screen: "${deferredMessage}"`);  }, [deferredMessage]);  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={deferredMessage} />    </>  );}
    ResultLoading…

    Line 5 starts with the draft "Happy birthday, Asha!". Line 6 passes "" as the initial value. The effect on lines 7 to 9 logs what's on the screen after each render. Press Run again and read the console:

    1. On screen: "": the first render used the initial value. CardDesigns got an empty message and returned its short note at once, so the box and the note appeared without waiting.
    2. Drawing 100 designs for "Happy birthday, Asha!": React then rendered in the background with the real value.
    3. On screen: "Happy birthday, Asha!": the designs reached the screen.

    Press Edit & run and delete , "" from line 6. Now the console starts with Drawing 100 designs, and the first thing on the screen is the finished designs: nothing appeared until they were drawn. Before React 19, useDeferredValue took only one argument, so older code can't do this.

    Deferring is not debouncing

    Before useDeferredValue, the usual fix for a slow list was to debounce the value: wait until the reader stops typing for a fixed time, then update the list. The Debounce and throttle lesson builds it in plain JavaScript. Here it is as a custom Hook, in the useDebouncedValue.js tab:

    The designs wait 300 ms after the last keyPop out in the code editor (opens in a new tab)JSX
    import { useState } from "react";import CardDesigns from "./CardDesigns.jsx";import { useDebouncedValue } from "./useDebouncedValue.js"; export default function CardEditor() {  const [message, setMessage] = useState("");  const debouncedMessage = useDebouncedValue(message, 300);  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={debouncedMessage} />    </>  );}
    ResultLoading…

    Line 7 gets a copy of message that only changes 300 milliseconds after the last change. In the tab, the effect on lines 5 to 8 starts a timer on every change, and its cleanup cancels the one before. Line 11 passes the debounced copy to the designs. Type Hey, stop, and the designs appear 0.3 seconds later. That works, but it's different from deferring in three ways:

    • A fixed wait. The designs always wait 300 ms, even on a fast laptop that could draw them at once. A deferred value has no wait of its own: the background render starts right after the box updates, so a fast computer catches up almost at once and a slow one later.
    • It still blocks. When the timer fires, the designs render in one piece. Type Hey, stop, and type again just as the designs start: the box freezes until they're done. A deferred render steps aside for your key.
    • It runs less often. That's where debouncing wins. Nothing runs until typing stops, so it's the right tool for work outside rendering, like a request per search or saving a draft. You can use both: debounce the request, defer the rendering.

    Go back to the playground in Defer the slow part, choose Debounced 300 ms, and press the button. The keys before the pause are quick, but the first key after it waits: it lands while the designs are drawing.

    How to read the table: each row is one question, answered for debouncing and for a deferred value.

    Debouncing a value or deferring it
    QuestionDebouncinguseDeferredValue
    When does the slow part update?A fixed time after the last key, like 300 ms, even on a fast computer.As soon as React has a free moment after the box updates. There's no fixed wait.
    Can a key interrupt the slow render?No. Once the render starts, the next key waits until it ends.Yes. React throws the unfinished render away and handles the key first.
    Does it send fewer requests?Yes. Nothing runs until typing stops.No. Every key still renders, so every new query still starts its search.
    What is it best for?Work outside rendering, like requests, saving a draft, or analytics.Slow rendering, and keeping old results on screen while new ones load.

    A transition or a deferred value?

    A transition also marks work as not urgent. The previous lesson, Transitions, covers useTransition in depth. The difference is what you wrap. A transition wraps a state update, so you need the setter. Here is the card editor with a transition instead:

    The same editor with a transitionPop out in the code editor (opens in a new tab)JSX
    import { useState, useTransition } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("");  const [previewMessage, setPreviewMessage] = useState("");  const [isPending, startTransition] = useTransition();   function handleChange(e) {    setMessage(e.target.value);    startTransition(() => {      setPreviewMessage(e.target.value);    });  }   return (    <>      <input value={message} onChange={handleChange} placeholder="Your message" />      {isPending && <p>Updating the designs…</p>}      <CardDesigns message={previewMessage} />    </>  );}
    ResultLoading…

    It needs two pieces of state. Line 10 sets message for the box, urgently: react.dev says a transition can't control a text box, because the box must show every key at once. Lines 11 to 13 set previewMessage for the designs inside startTransition. Line 19 uses isPending, which is true while the transition is still rendering, to show Updating the designs…. Type quickly: it behaves like the deferred version, and the note appears while you type.

    A deferred value wraps a value, so it works where you can't reach the setter, like a component that only gets the message as a prop:

    A panel that defers the prop it receivesPop out in the code editor (opens in a new tab)JSX
    import { useDeferredValue, useState } from "react";import CardDesigns from "./CardDesigns.jsx"; // This panel doesn't own the message. It only gets it as a prop.function DesignsPanel({ message }) {  const deferredMessage = useDeferredValue(message);  return <CardDesigns message={deferredMessage} />;} export default function CardEditor() {  const [message, setMessage] = useState("");  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <DesignsPanel message={message} />    </>  );}
    ResultLoading…

    Lines 5 to 8 are DesignsPanel. It doesn't own the message: line 15 passes it in as a prop. Line 6 defers it, and line 7 draws the designs from the deferred copy. The panel needs no change in its parent. The same goes for a value from a library's Hook, like a router's search parameters.

    Two cases. Left: you own the setter, so you wrap the update in startTransition inside your handler. Right: you only receive the value as a prop, so you wrap the value in useDeferredValue where you read it.You have the setterhandleChange(e)startTransition(() => set…)useTransitionYou only get the valueDesignsPanel({ message })useDeferredValue(message)useDeferredValue
    Left: your code makes the change, so wrap the update in startTransition. Right: a value arrives from somewhere else, so defer the value with useDeferredValue where you read it.

    One more rule: when an update already runs inside a transition, useDeferredValue returns the new value at once. The update is already in the background, so there's nothing to defer and no extra render.

    How to read the table: each row is one question, answered for each tool.

    useTransition or useDeferredValue
    QuestionuseTransitionuseDeferredValue
    What do you wrap?The state update: startTransition(() => setPreviewMessage(…)).A value: useDeferredValue(message).
    What do you need to have?The setter, in the code that makes the change.Only the value, for example a prop from a parent.
    How do you show it's still working?isPending is true until the new render is on screen.message !== deferredMessage is true while the old value shows.
    Can a text box's own value use it?No. A text box must update on every key, so its own state stays outside the transition.The box keeps using message. Only the slow part reads the deferred copy.

    A product search that stays fast

    Here is a search the way you'd ship it: one piece of state, a deferred copy for the results, a fallback only for the very first load, and a fade that only shows when the wait is long enough to notice.

    A product search with deferred resultsPop out in the code editor (opens in a new tab)JSX
    import { Suspense, useDeferredValue, useState } from "react";import SearchResults from "./SearchResults.jsx"; export default function ProductSearch() {  const [query, setQuery] = useState("");  const deferredQuery = useDeferredValue(query);  const isStale = query !== deferredQuery;   return (    <>      <label>        Search products <input value={query} onChange={(e) => setQuery(e.target.value)} />      </label>      <Suspense fallback={<p>Loading products…</p>}>        <section          aria-busy={isStale}          style={{            opacity: isStale ? 0.5 : 1,            transition: isStale ? "opacity 0.2s 0.2s" : "opacity 0s",          }}        >          <SearchResults query={deferredQuery} />        </section>      </Suspense>    </>  );}
    ResultLoading…

    Lines 6 and 7 make the deferred query and compare it with the real one. Line 12 is the box, with its label on line 11, so screen readers announce it. The boundary on line 14 shows Loading products… only the first time. Line 16 sets aria-busy on the results while they're old, which tells screen readers that the area is updating. Lines 18 and 19 fade the old results, but the fade waits 0.2 seconds before it starts, so a quick answer never flickers. Line 22 renders the results for the deferred query.

    Type tea: the box keeps up, and the old list fades only if the new one takes longer than 0.2 seconds. Here every search takes 0.8 seconds, so you'll see it fade. Delete a letter: that query is in the cache, so the results switch back at once.

    Urgent, transition, or deferred value?
    • setMessage(e.target.value) for the text box's own value
    • A Like counter that must change the moment it's clicked
    • Your own click handler calls setTab("reviews"), and the reviews tab is slow
    • You want isPending to show a spinner next to the button you clicked
    • ResultsChart gets query as a prop from a parent you don't own, and draws slowly
    • A value from a library's hook, like a router's search parameters, drives a slow list
    • Results under a search box, which suspend while each query loads, from one query state
    • A link click that moves to another page in your own router
    Try it yourself
    0 of 8 correct

    Sort each situation: keep it a plain urgent update, wrap the update in startTransition, or defer the value with useDeferredValue.

    Choose a category for every card. You can change an answer at any time; Reset clears them all.

    Common mix-ups

    • "useDeferredValue makes the slow component faster." No. Each render takes just as long. It moves that render into the background, after the urgent one, and drops it when a newer value comes.
    • "It waits a fixed time, like a debounce." No. There's no timer. The background render starts right after the urgent one, so a fast device catches up almost at once.
    • "It sends fewer requests." No. Every new value still renders, and every new query still starts its search. Debounce the requests if you need fewer.
    • "It helps even without memo." No. Without memo (or the React Compiler), the urgent render draws the slow part again with the old value, and the box waits for it.
    • "The first render is deferred too." No, not by default. The first render uses the value itself, unless you pass an initialValue, which React 19 added.
    • "A suspending background render shows the fallback." No. React keeps the old content on screen until the new content is ready. The fallback only shows when there's nothing old to keep, like the first load.
    • "You can defer an object you make while rendering." No. A new object, like useDeferredValue({ query }), is different on every render, so React schedules a background render every time. Defer strings, numbers, or objects made outside rendering.

    Practice exercises

    The card exercises use the same 100 slow designs, in their CardDesigns.jsx tab, and the search exercises the same pretend server, in data.js. Change the code with Edit & run; the result updates as you type.

    Exercise 1 · Warm-upPredict the first render after a key

    The box is empty. You type H. In the first render that follows, what is deferredMessage? Answer, then press Edit & run, type, and check the console.

    import { useDeferredValue, useState } from "react";
    
    export default function CardEditor() {
      const [message, setMessage] = useState("");
      const deferredMessage = useDeferredValue(message);
      console.log(`message: "${message}", deferredMessage: "${deferredMessage}"`);
      return (
        <>
          <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />
          <p>Box: {message}</p>
          <p>Preview: {deferredMessage}</p>
        </>
      );
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 2 · Warm-upDefer the designs

      Typing lags because every key redraws the 100 designs. Make the box fast with useDeferredValue. Which value do you pass to CardDesigns after your change?

      import { useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("");  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={message} />    </>  );}
      ResultLoading…

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 3 · PracticeFind why the deferred value doesn't help

        This editor uses useDeferredValue, yet typing still lags after the first word. Look at the console while you type, then fix the CardDesigns.jsx tab. Which function from React fixes it?

        import { useDeferredValue, useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("");  const deferredMessage = useDeferredValue(message);  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <CardDesigns message={deferredMessage} />    </>  );}
        ResultLoading…

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 4 · PracticeTell the reader the results are old

          The old results stay while new ones load, but nothing says they're old. Show Searching… under the box while they're stale. While the old results show, what does query !== deferredQuery give?

          import { Suspense, useDeferredValue, useState } from "react";import SearchResults from "./SearchResults.jsx"; export default function ProductSearch() {  const [query, setQuery] = useState("");  const deferredQuery = useDeferredValue(query);  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search products" />      <Suspense fallback={<p>Loading…</p>}>        <SearchResults query={deferredQuery} />      </Suspense>    </>  );}
          ResultLoading…

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 5 · ChallengeDefer a prop you don't own

            Make typing fast by changing only DesignsPanel, not CardEditor. Which Hook do you call inside DesignsPanel?

            import { useState } from "react";import CardDesigns from "./CardDesigns.jsx"; // This panel doesn't own the message. It only gets it as a prop.function DesignsPanel({ message }) {  return <CardDesigns message={message} />;} export default function CardEditor() {  const [message, setMessage] = useState("");  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      <DesignsPanel message={message} />    </>  );}
            ResultLoading…

            Answer, then press Check. Spacing and letter case don’t matter.

              Exercise 6 · ChallengeOpen the saved draft without a wait

              The editor opens with a saved draft, and nothing shows until all the designs are drawn. Make the first render quick. After your change, what does the line above the designs say during the first render?

              import { useDeferredValue, useState } from "react";import CardDesigns from "./CardDesigns.jsx"; export default function CardEditor() {  const [message, setMessage] = useState("Happy birthday, Asha!");  const deferredMessage = useDeferredValue(message);  const isStale = message !== deferredMessage;  return (    <>      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your message" />      {isStale && <p>Drawing your designs…</p>}      <CardDesigns message={deferredMessage} />    </>  );}
              ResultLoading…

              Answer, then press Check. Spacing and letter case don’t matter.

                Check your understanding

                For each question, ask which render you're in: the urgent one, or the background one that can be thrown away.

                The deferred values quiz · 8 questionsScore: first tries count
                1. Question 1 of 8These lines are at the top of a component. The reader types H in the box. What do the renders that follow log?

                  Read the code, then predictJSX
                  const [message, setMessage] = useState("");
                  const deferredMessage = useDeferredValue(message);
                  console.log({ message, deferredMessage });
                  // The reader types "H" in the box.

                  Choose an answer to see the explanation.

                2. Question 2 of 8React is drawing the 100 designs in the background when you press another key. What happens to that drawing?

                  Choose an answer to see the explanation.

                3. Question 3 of 8Why does CardDesigns need memo for useDeferredValue to help?

                  Choose an answer to see the explanation.

                4. Question 4 of 8The results read deferredQuery, and the results for the new query suspend while they load. What does the reader see meanwhile?

                  Choose an answer to see the explanation.

                5. Question 5 of 8What is deferredMessage during the first render of this component?

                  Read the code, then predictJSX
                  const [message] = useState("Tea");
                  const deferredMessage = useDeferredValue(message, "");
                  // What is deferredMessage during the first render?

                  Choose an answer to see the explanation.

                6. Question 6 of 8How is useDeferredValue different from debouncing a value by 300 ms?

                  Choose an answer to see the explanation.

                7. Question 7 of 8This update runs inside startTransition. In the next render, what does deferredQuery hold?

                  Read the code, then predictJSX
                  const deferredQuery = useDeferredValue(query);
                  // …
                  startTransition(() => setQuery("cake"));

                  Choose an answer to see the explanation.

                8. Question 8 of 8A component gets filters as a prop from its parent and draws a slow chart from it. Which tool fits inside that component?

                  Choose an answer to see the explanation.

                Key takeaways

                • useDeferredValue(value) returns a copy that lags behind value. Give the urgent part the value and the slow part the copy.
                • Each change makes two renders: an urgent one with the old copy, then a background one with the new copy, which a newer change throws away.
                • Wrap the slow component in memo (or use the React Compiler), so the urgent render can skip it.
                • With Suspense, a background render that suspends keeps the old content on screen. Dim it with value !== deferredValue.
                • In React 19, useDeferredValue(value, initialValue) makes the first render quick too.
                • It isn't a debounce: there's no fixed wait, it can be interrupted, and it doesn't send fewer requests.
                • Use a transition when you have the setter, and a deferred value when you only have the value.

                Remember the one-liner.
                The box gets the value, the slow part gets the deferred copy, and React drops any drawing that a newer key makes old.

                Coming next: Hiding and pre-rendering with Activity. It keeps a hidden part of the page alive, with its state, or renders it in the background before anyone opens it.

                CompleteFrontend Clear concepts. Working examples.