Keep the UI Snappy While an Expensive Render Catches Up
useDeferredValue
`useDeferredValue` returns a "lagging" copy of a value. The urgent parts of the UI update immediately; the expensive parts catch up on the next idle window.
What you'll learn
- Defer an expensive derived value
- Recognize when deferring helps
useDeferredValue(value) returns a deferred copy of value. The
component renders once with the new urgent value (input, focus,
small UI) and the OLD deferred value, then renders again with the
new deferred value when there’s time.
The Classic Use Case
A search box with a heavy results list. You want every keystroke in the input to be instant, even if rendering matching results is slow.
function Search() {
const [query, setQuery] = useState("");
const deferred = useDeferredValue(query);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<Results query={deferred} /> {/* renders with lagging value */}
</>
);
} The input updates immediately because the urgent path is just
“render new input value”. <Results> re-renders only when React
has spare cycles.
Pair With memo
The trick only pays off if the expensive child can skip re-renders when its props haven’t changed:
const Results = memo(function Results({ query }) {
// expensive filter / render
}); Without memo, the child re-renders even when deferred matches
the previous value — defeating the purpose.
How It Feels
While the user types fast, the input always reflects the latest character. The results list shows the previous query for a few frames, then catches up. Way better than freezing the input.
Not a Debounce
useDeferredValue doesn’t delay updates by a fixed time — it
delays them until the browser is idle. If the heavy work is fast,
the lag disappears. If slow, the lag grows.
For “wait 250ms after typing stops” semantics, use a debounce
(useDebounce custom hook).
Up Next
The flip side — explicitly marking a state update as non-urgent.
useTransition →