function Search({ items }) {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<SlowList items={items} query={deferredQuery} />
</>
);
}
const SlowList = memo(function SlowList({ items, query }) { /* ... */ });
Both keep the input fast, but in different ways.
Debouncing waits until the user stops typing for a fixed time, for example 300 ms, and only then updates the list. It does less work in total. But the list always lags by at least 300 ms, even on a fast phone. Use it when each update is costly outside React too, like a network request per search.
useDeferredValue lets React update the input first, at high priority. Then React re-renders the list with the new value in the background, at low priority. If the user types again, React throws away that unfinished render and starts with the newer value. So:
- On a fast device, the list updates almost at once.
- On a slow device, the list falls behind, but typing never freezes.
- There is no fixed delay to tune.
In the code, SlowList must be wrapped in memo. During the urgent render, deferredQuery still has the old value. memo lets React skip SlowList in that render. Without memo, the slow list renders in the urgent update too, and nothing improves.
In short: use useDeferredValue (or useTransition) for slow rendering. Use debouncing for slow or costly side effects like fetches. You can use both: debounce the fetch and defer the render.
- Debouncing waits for a pause in typing, so the list always lags by the delay.
- Debouncing is best for costly side effects like a fetch per keystroke.
useDeferredValuerenders the input first and the list later, at low priority.- React throws away an unfinished deferred render when a newer value arrives.
- The slow component must be wrapped in
memo, or deferring does not help.