useDeferredValue

useDeferredValue(value, initialValue?) returns a copy of value that lags behind during urgent updates. On the render where value changes, React first returns the previous value again, then re-renders once more in the background – at low priority, interruptible by anything more urgent – with the latest value, swapping it in once that background render is ready. The optional second argument, initialValue (added in React 19), is what gets returned specifically on the very first render, since there's no older value yet to fall back to; leave it out and the first render just returns value itself, unlagged.

Where useTransition lets you mark a state update you're making as low priority, useDeferredValue instead takes a value you may not control the setting of at all – a prop passed down from a parent, or state whose setter lives elsewhere entirely – and hands you back a lagging copy to render with, without needing any say over how or where that value gets set.

Type into the field below (try a few characters quickly). SlowList is wrapped in React.memo and only receives the deferred (lagging) query, so each keystroke re-renders just the input instantly; the list underneath dims to show it's stale, and only re-filters once typing pauses, instead of re-filtering all 150 items on every single keystroke.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head><body>
<div></div>
<script type="text/babel">
function SlowItem({ text }) {
  const start = performance.now();
  while (performance.now() - start < 1) {} // simulate an expensive item to render
  return <li>{text}</li>;
}
const SlowList = React.memo(function SlowList({ query }) {
  const items = [];
  for (let i = 0; i < 150; i++) {
    const text = 'Item #' + i;
    if (text.toLowerCase().includes(query.toLowerCase())) {
      items.push(<SlowItem key={i} text={text} />);
    }
  }
  return <ul>{items}</ul>;
});
function App() {
  const [query, setQuery] = React.useState('');
  const deferredQuery = React.useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <React.Fragment>
      <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Filter..." />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <SlowList query={deferredQuery} />
      </div>
    </React.Fragment>
  );}
const root = ReactDOM.createRoot(document.querySelector("div"));
root.render(<App/>);
</script>
</body></html>

Pairing useDeferredValue with React.memo() on the component that receives it is what makes this work: without memo, SlowList's expensive body would re-run on every keystroke regardless of whether its query prop had actually changed yet, defeating the whole point.