useDeferredValue
useDeferredValue
is a React Hook that lets you defer updating a part of the UI.
const deferredValue = useDeferredValue(value)
Reference
useDeferredValue(value, initialValue?)
Call useDeferredValue
at the top level of your component to get a deferred version of that value.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Parameters
value
: The value you want to defer. It can have any type.- optional
initialValue
: A value to use during the initial render of a component. If this option is omitted,useDeferredValue
will not defer during the initial render, because there’s no previous version ofvalue
that it can render instead.
Returns
currentValue
: During the initial render, the returned deferred value will be theinitialValue
, or the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value).
Caveats
-
When an update is inside a Transition,
useDeferredValue
always returns the newvalue
and does not spawn a deferred render, since the update is already deferred. -
The values you pass to
useDeferredValue
should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it touseDeferredValue
, it will be different on every render, causing unnecessary background re-renders. -
When
useDeferredValue
receives a different value (compared withObject.is
), in addition to the current render (when it still uses the previous value), it schedules a re-render in the background with the new value. The background re-render is interruptible: if there’s another update to thevalue
, React will restart the background re-render from scratch. For example, if the user is typing into an input faster than a chart receiving its deferred value can re-render, the chart will only re-render after the user stops typing. -
useDeferredValue
is integrated with<Suspense>
. If the background update caused by a new value suspends the UI, the user will not see the fallback. They will see the old deferred value until the data loads. -
useDeferredValue
does not by itself prevent extra network requests. -
There is no fixed delay caused by
useDeferredValue
itself. As soon as React finishes the original re-render, React will immediately start working on the background re-render with the new deferred value. Any updates caused by events (like typing) will interrupt the background re-render and get prioritized over it. -
The background re-render caused by
useDeferredValue
does not fire Effects until it’s committed to the screen. If the background re-render suspends, its Effects will run after the data loads and the UI updates.
Usage
Showing stale content while fresh content is loading
Call useDeferredValue
at the top level of your component to defer updating some part of your UI.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
During the initial render, the deferred value will be the same as the value you provided.
During updates, the deferred value will “lag behind” the latest value. In particular, React will first re-render without updating the deferred value, and then try to re-render with the newly received value in the background.
Let’s walk through an example to see when this is useful.
In this example, the SearchResults
component suspends while fetching the search results. Try typing "a"
, waiting for the results, and then editing it to "ab"
. The results for "a"
get replaced by the loading fallback.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
A common alternative UI pattern is to defer updating the list of results and to keep showing the previous results until the new results are ready. Call useDeferredValue
to pass a deferred version of the query down:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
The query
will update immediately, so the input will display the new value. However, the deferredQuery
will keep its previous value until the data has loaded, so SearchResults
will show the stale results for a bit.
Enter "a"
in the example below, wait for the results to load, and then edit the input to "ab"
. Notice how instead of the Suspense fallback, you now see the stale result list until the new results have loaded:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
Deep Dive
You can think of it as happening in two steps:
-
First, React re-renders with the new
query
("ab"
) but with the olddeferredQuery
(still"a")
. ThedeferredQuery
value, which you pass to the result list, is deferred: it “lags behind” thequery
value. -
In the background, React tries to re-render with both
query
anddeferredQuery
updated to"ab"
. If this re-render completes, React will show it on the screen. However, if it suspends (the results for"ab"
have not loaded yet), React will abandon this rendering attempt, and retry this re-render again after the data has loaded. The user will keep seeing the stale deferred value until the data is ready.
The deferred “background” rendering is interruptible. For example, if you type into the input again, React will abandon it and restart with the new value. React will always use the latest provided value.
Note that there is still a network request per each keystroke. What’s being deferred here is displaying results (until they’re ready), not the network requests themselves. Even if the user continues typing, responses for each keystroke get cached, so pressing Backspace is instant and doesn’t fetch again.
Indicating that the content is stale
In the example above, there is no indication that the result list for the latest query is still loading. This can be confusing to the user if the new results take a while to load. To make it more obvious to the user that the result list does not match the latest query, you can add a visual indication when the stale result list is displayed:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
With this change, as soon as you start typing, the stale result list gets slightly dimmed until the new result list loads. You can also add a CSS transition to delay dimming so that it feels gradual, like in the example below:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1, transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear' }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
Deferring re-rendering for a part of the UI
You can also apply useDeferredValue
as a performance optimization. It is useful when a part of your UI is slow to re-render, there’s no easy way to optimize it, and you want to prevent it from blocking the rest of the UI.
Imagine you have a text field and a component (like a chart or a long list) that re-renders on every keystroke:
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
First, optimize SlowList
to skip re-rendering when its props are the same. To do this, wrap it in memo
:
const SlowList = memo(function SlowList({ text }) {
// ...
});
However, this only helps if the SlowList
props are the same as during the previous render. The problem you’re facing now is that it’s slow when they’re different, and when you actually need to show different visual output.
Concretely, the main performance problem is that whenever you type into the input, the SlowList
receives new props, and re-rendering its entire tree makes the typing feel janky. In this case, useDeferredValue
lets you prioritize updating the input (which must be fast) over updating the result list (which is allowed to be slower):
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
This does not make re-rendering of the SlowList
faster. However, it tells React that re-rendering the list can be deprioritized so that it doesn’t block the keystrokes. The list will “lag behind” the input and then “catch up”. Like before, React will attempt to update the list as soon as possible, but will not block the user from typing.
Example 1 of 2: Deferred re-rendering of the list
In this example, each item in the SlowList
component is artificially slowed down so that you can see how useDeferredValue
lets you keep the input responsive. Type into the input and notice that typing feels snappy while the list “lags behind” it.
import { useState, useDeferredValue } from 'react'; import SlowList from './SlowList.js'; export default function App() { const [text, setText] = useState(''); const deferredText = useDeferredValue(text); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <SlowList text={deferredText} /> </> ); }
Deep Dive
There are two common optimization techniques you might have used before in this scenario:
- Debouncing means you’d wait for the user to stop typing (e.g. for a second) before updating the list.
- Throttling means you’d update the list every once in a while (e.g. at most once a second).
While these techniques are helpful in some cases, useDeferredValue
is better suited to optimizing rendering because it is deeply integrated with React itself and adapts to the user’s device.
Unlike debouncing or throttling, it doesn’t require choosing any fixed delay. If the user’s device is fast (e.g. powerful laptop), the deferred re-render would happen almost immediately and wouldn’t be noticeable. If the user’s device is slow, the list would “lag behind” the input proportionally to how slow the device is.
Also, unlike with debouncing or throttling, deferred re-renders done by useDeferredValue
are interruptible by default. This means that if React is in the middle of re-rendering a large list, but the user makes another keystroke, React will abandon that re-render, handle the keystroke, and then start rendering in the background again. By contrast, debouncing and throttling still produce a janky experience because they’re blocking: they merely postpone the moment when rendering blocks the keystroke.
If the work you’re optimizing doesn’t happen during rendering, debouncing and throttling are still useful. For example, they can let you fire fewer network requests. You can also use these techniques together.