Skip to content
React rc hooks 4 min read

useDeferredValue

useDeferredValue lets you keep the UI responsive while an expensive part of the screen catches up to a fast-changing value. You hand it a value, and it returns a possibly-stale copy that lags behind during heavy renders. The fast inputs (like a text field) stay snappy, while the slow children re-render in the background using React’s concurrent renderer. It is one of the simplest tools for fixing the classic “typing feels laggy because a big list re-renders on every keystroke” problem.

How it works

You call useDeferredValue(value) and use the returned deferredValue to drive the slow part of your tree. On the first render the deferred value equals the value you passed. After that, when the input value changes, React renders the component twice: once immediately with the old deferred value (so urgent UI updates right away), and then again in the background with the new deferred value. If the value changes again before the background render finishes, React throws away the in-progress work and restarts — so you never block on stale output.

import { useState, useDeferredValue } from "react";
import SearchResults from "./SearchResults";

export default function SearchPage() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products…"
      />
      {/* The input uses the fresh `query` and stays responsive.
          The expensive list reads `deferredQuery` and lags behind. */}
      <SearchResults query={deferredQuery} />
    </div>
  );
}

The crucial detail: the <input> binds to the fresh query, so typing always feels instant. <SearchResults> reads deferredQuery, so its expensive render is deprioritized and can be interrupted by the next keystroke.

A search-results example

To make the deferral meaningful, the slow child needs to actually be slow. Memoizing it ensures it only re-renders when its props change, which is what allows the deferred value to gate its work.

import { memo } from "react";

const SearchResults = memo(function SearchResults({ query }) {
  // Pretend filtering this list is expensive.
  const items = filterProducts(query);

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

function filterProducts(query) {
  const q = query.toLowerCase();
  return PRODUCTS.filter((p) => p.name.toLowerCase().includes(q));
}

export default SearchResults;

Without memo, the child re-renders on every parent render regardless of whether query changed, and the deferral buys you nothing.

Showing a stale indicator

Because the deferred value lags, you can detect the “in between” moment by comparing it to the live value and dim the stale content.

import { useState, useDeferredValue } from "react";

export default function SearchPage() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <div style={{ opacity: isStale ? 0.5 : 1, transition: "opacity 0.2s" }}>
        <SearchResults query={deferredQuery} />
      </div>
    </div>
  );
}

Tip: React 19 also added an initialValue argument — useDeferredValue(value, initialValue) — which lets the very first render use a lighter placeholder before deferring to the real value.

useDeferredValue vs useTransition

Both defer work using the concurrent renderer, but they wrap different things. useTransition defers a state update you control, so it suits cases where you own the setState call. useDeferredValue defers a value you receive, so it suits cases where the value comes from props or from state you would rather not restructure.

AspectuseDeferredValueuseTransition
You wrapA valueA state update (setState inside startTransition)
Need access to the setter?NoYes
Pending flagDerive via value !== deferredValueBuilt-in isPending boolean
Best forProps you don’t own, third-party valuesUpdates you trigger yourself (tab switches, filters)
ReturnsThe deferred value[isPending, startTransition]

If you own the update, useTransition is usually clearer. If you only have a value handed to you — for example from a context or a parent — reach for useDeferredValue.

When it does not help

useDeferredValue does not make rendering faster; it only changes when the slow render happens and lets it be interrupted. If your child is slow because of a synchronous, uninterruptible computation that always blocks the main thread, you still need to optimize that work (memoization, virtualization, or moving it to a Web Worker). The hook also has no effect if the child is not memoized, since it would re-render anyway.

Best practices

  • Bind fast inputs to the live value and only feed the deferred value to expensive children.
  • Wrap the slow child in React.memo so the deferred value can actually gate its re-renders.
  • Use value !== deferredValue to show a subtle stale indicator instead of a blocking spinner.
  • Prefer useTransition when you own the state update; reach for useDeferredValue for values you only receive.
  • Avoid creating a new object or array as the deferred value’s source each render — it defeats memoization.
  • Pair with virtualization for very large lists; deferral schedules the work but does not shrink it.
Last updated June 14, 2026
Was this helpful?