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.
- 01Keep 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. - 02Show 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 aninitialValue. - 03Pick the right toolTell deferring from debouncing, and choose between
useDeferredValueanduseTransitionby 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.
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> </> );}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:
message: "H", deferredMessage: "". React renders at once with the newmessage, butuseDeferredValuestill returns the old value. This render is urgent: it puts the key in the box.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.
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.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:
messageupdates 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
deferredMessagein 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
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:
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} /> </> );}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:
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} /> </> );}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:
- The urgent render has the new
messageand the olddeferredMessage.CardDesignsgets the same message as last time, somemoskips it. The render takes almost no time, and the box shows your key. - The background render has the new
deferredMessage, soCardDesignsdraws 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.
"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.
plainType a message to see the designs.
no keys yetChoose 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.
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:
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> </> );}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:
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> </> );}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:
- The urgent render has
queryset to"ca"anddeferredQuerystill"c". Those results are in the cache, so nothing suspends, and the box updates. - 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.
"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:
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} /> </> );}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:
On screen: "": the first render used the initial value.CardDesignsgot an empty message and returned its short note at once, so the box and the note appeared without waiting.Drawing 100 designs for "Happy birthday, Asha!": React then rendered in the background with the real value.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:
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} /> </> );}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.
| Question | Debouncing | useDeferredValue |
|---|---|---|
| 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:
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} /> </> );}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:
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} /> </> );}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.
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.
| Question | useTransition | useDeferredValue |
|---|---|---|
| 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.
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> </> );}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.
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
isPendingto show a spinner next to the button you clicked ResultsChartgetsqueryas 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
querystate - A link click that moves to another page in your own router
Sort each situation: keep it a plain urgent update, wrap the update in startTransition, or defer the value with useDeferredValue.
Common mix-ups
- "
useDeferredValuemakes 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. Withoutmemo(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.
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>
</>
);
}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> </> );}In the first render after the key, message is "H" and deferredMessage is still "". The console's first new line says so. The background render then sets both to "H".
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} /> </> );}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} /> </> );}Line 6 makes the deferred copy and line 10 gives it to the designs. The box keeps message, so it updates in the urgent render, and the designs catch up in the background.
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} /> </> );}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} /> </> );}Without memo, the urgent render draws all 100 designs again for the old message, and the box waits for it. App.jsx stays the same; in the CardDesigns.jsx tab, line 1 imports memo and line 22 exports memo(CardDesigns), so React skips the designs when the message is the same, and only the background render draws.
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> </> );}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" /> {isStale && <p>Searching…</p>} <Suspense fallback={<p>Loading…</p>}> <SearchResults query={deferredQuery} /> </Suspense> </> );}Line 7 is true while deferredQuery still holds the old query, so line 11 shows Searching… until the new results are on screen.
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} /> </> );}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} /> </> );}Line 6 defers the prop inside the panel, and line 7 draws from it. The parent doesn't change at all, and the box stays fast because the designs are already wrapped in memo.
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} /> </> );}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} /> </> );}With useDeferredValue(message, ""), the first render uses "": the designs return their short note at once, and isStale is true, so line 11 shows Drawing your designs…. Then the background render draws the draft.
Check your understanding
For each question, ask which render you're in: the urgent one, or the background one that can be thrown away.
Question 1 of 8These lines are at the top of a component. The reader types
Hin the box. What do the renders that follow log?Read the code, then predictJSXconst [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.
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.
Question 3 of 8Why does
CardDesignsneedmemoforuseDeferredValueto help?Choose an answer to see the explanation.
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.
Question 5 of 8What is
deferredMessageduring the first render of this component?Read the code, then predictJSXconst [message] = useState("Tea"); const deferredMessage = useDeferredValue(message, ""); // What is deferredMessage during the first render?Choose an answer to see the explanation.
Question 6 of 8How is
useDeferredValuedifferent from debouncing a value by 300 ms?Choose an answer to see the explanation.
Question 7 of 8This update runs inside
startTransition. In the next render, what doesdeferredQueryhold?Read the code, then predictJSXconst deferredQuery = useDeferredValue(query); // … startTransition(() => setQuery("cake"));Choose an answer to see the explanation.
Question 8 of 8A component gets
filtersas 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 behindvalue. 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.