MENU
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.
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.