useDeferredValue

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.

3 min read Level 3/5 #react#useDeferredValue#concurrent
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 →