useDeferredValue: Keep Input Snappy During Slow Renders
Defer updating slow results so typing stays responsive.
I had a search box where every keystroke re-filtered a huge list, and the input lagged because the keystroke and the expensive render ran at the same priority. useDeferredValue separates them: the input updates immediately, and the slow list renders when the browser has time for it.
The version that stutters
The raw query flows straight into SlowList, so the 10,000-item filter runs on every keypress and typing stutters.
import { useState } from "react";
function SlowList({ query }) {
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
const filtered = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
return (
<ul>
{filtered.slice(0, 100).map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
function App() {
const [query, setQuery] = useState("");
return (
<div>
<h1>useDeferredValue</h1>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<SlowList query={query} />
</div>
);
}
export default App;
Deferring the query
The input stays bound to query. The list gets a deferred copy instead:
import { useState, useDeferredValue } from "react";
function SlowList({ query }) {
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
const filtered = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
return (
<ul>
{filtered.slice(0, 100).map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<div>
<h1>useDeferredValue</h1>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<SlowList query={deferredQuery} />
</div>
);
}
export default App;
useDeferredValue(query) returns a value that trails the real one when renders are expensive. The input keeps updating at full priority, and the list re-renders with the older deferred value until the browser has time to catch up. If the slow child renders from the deferred value, wrap it in memo as well, where appropriate, to avoid unnecessary work.
When I don’t defer
Critical UI stays out of this. Validation errors must appear the instant I type, not after the slow render finishes. And I measure before and after, deferring only what’s actually slow.