The useDeferredValue hook defers updating part of the UI to keep the interface responsive. Here's how to use it.
Basic Usage#
1import { useState, useDeferredValue } from 'react';
2
3function SearchResults() {
4 const [query, setQuery] = useState('');
5 const deferredQuery = useDeferredValue(query);
6
7 // Input responds immediately
8 // Results update with deferred value
9 return (
10 <div>
11 <input
12 value={query}
13 onChange={(e) => setQuery(e.target.value)}
14 placeholder="Search..."
15 />
16 <Results query={deferredQuery} />
17 </div>
18 );
19}
20
21function Results({ query }) {
22 // Expensive computation or filtering
23 const results = searchItems(query);
24
25 return (
26 <ul>
27 {results.map((item) => (
28 <li key={item.id}>{item.name}</li>
29 ))}
30 </ul>
31 );
32}Showing Stale Content#
1import { useState, useDeferredValue, memo } from 'react';
2
3function App() {
4 const [text, setText] = useState('');
5 const deferredText = useDeferredValue(text);
6
7 // Check if showing stale content
8 const isStale = text !== deferredText;
9
10 return (
11 <div>
12 <input
13 value={text}
14 onChange={(e) => setText(e.target.value)}
15 />
16
17 <div style={{ opacity: isStale ? 0.5 : 1 }}>
18 <SlowList text={deferredText} />
19 </div>
20 </div>
21 );
22}
23
24// Memoize to prevent re-renders with same deferred value
25const SlowList = memo(function SlowList({ text }) {
26 const items = [];
27 for (let i = 0; i < 500; i++) {
28 items.push(<SlowItem key={i} text={text} />);
29 }
30 return <ul>{items}</ul>;
31});
32
33function SlowItem({ text }) {
34 // Simulate slow render
35 const startTime = performance.now();
36 while (performance.now() - startTime < 1) {
37 // Artificial delay
38 }
39
40 return <li>{text}</li>;
41}With Suspense#
1import { Suspense, useState, useDeferredValue } from 'react';
2
3function SearchApp() {
4 const [query, setQuery] = useState('');
5 const deferredQuery = useDeferredValue(query);
6
7 const isStale = query !== deferredQuery;
8
9 return (
10 <div>
11 <SearchInput value={query} onChange={setQuery} />
12
13 <Suspense fallback={<Loading />}>
14 <div style={{ opacity: isStale ? 0.7 : 1 }}>
15 <SearchResults query={deferredQuery} />
16 </div>
17 </Suspense>
18 </div>
19 );
20}
21
22// SearchResults can suspend while fetching
23function SearchResults({ query }) {
24 const results = use(fetchResults(query));
25
26 return (
27 <ul>
28 {results.map((result) => (
29 <li key={result.id}>{result.title}</li>
30 ))}
31 </ul>
32 );
33}Filter Performance#
1import { useState, useDeferredValue, useMemo, memo } from 'react';
2
3function ProductList({ products }) {
4 const [filter, setFilter] = useState('');
5 const [category, setCategory] = useState('all');
6
7 const deferredFilter = useDeferredValue(filter);
8 const deferredCategory = useDeferredValue(category);
9
10 const isStale =
11 filter !== deferredFilter || category !== deferredCategory;
12
13 return (
14 <div>
15 <div className="filters">
16 <input
17 value={filter}
18 onChange={(e) => setFilter(e.target.value)}
19 placeholder="Filter products..."
20 />
21
22 <select
23 value={category}
24 onChange={(e) => setCategory(e.target.value)}
25 >
26 <option value="all">All Categories</option>
27 <option value="electronics">Electronics</option>
28 <option value="clothing">Clothing</option>
29 </select>
30 </div>
31
32 <div className={isStale ? 'stale' : ''}>
33 <FilteredProducts
34 products={products}
35 filter={deferredFilter}
36 category={deferredCategory}
37 />
38 </div>
39 </div>
40 );
41}
42
43const FilteredProducts = memo(function FilteredProducts({
44 products,
45 filter,
46 category,
47}) {
48 const filtered = useMemo(() => {
49 return products.filter((product) => {
50 const matchesFilter = product.name
51 .toLowerCase()
52 .includes(filter.toLowerCase());
53 const matchesCategory =
54 category === 'all' || product.category === category;
55 return matchesFilter && matchesCategory;
56 });
57 }, [products, filter, category]);
58
59 return (
60 <div className="product-grid">
61 {filtered.map((product) => (
62 <ProductCard key={product.id} product={product} />
63 ))}
64 </div>
65 );
66});Chart Updates#
1import { useState, useDeferredValue, memo } from 'react';
2
3function Dashboard() {
4 const [data, setData] = useState(initialData);
5 const [range, setRange] = useState({ start: 0, end: 100 });
6
7 const deferredRange = useDeferredValue(range);
8 const isStale = range !== deferredRange;
9
10 return (
11 <div>
12 <RangeSlider
13 value={range}
14 onChange={setRange}
15 />
16
17 <div style={{ opacity: isStale ? 0.6 : 1 }}>
18 <ExpensiveChart
19 data={data}
20 range={deferredRange}
21 />
22 </div>
23 </div>
24 );
25}
26
27const ExpensiveChart = memo(function ExpensiveChart({ data, range }) {
28 // Expensive rendering with many data points
29 const filteredData = data.slice(range.start, range.end);
30
31 return (
32 <svg width="800" height="400">
33 {filteredData.map((point, i) => (
34 <circle
35 key={i}
36 cx={point.x}
37 cy={point.y}
38 r="2"
39 fill="blue"
40 />
41 ))}
42 </svg>
43 );
44});Table Filtering#
1import { useState, useDeferredValue, useMemo, memo } from 'react';
2
3function DataTable({ rows }) {
4 const [sortColumn, setSortColumn] = useState('name');
5 const [sortDirection, setSortDirection] = useState('asc');
6 const [search, setSearch] = useState('');
7
8 const deferredSearch = useDeferredValue(search);
9 const isSearching = search !== deferredSearch;
10
11 return (
12 <div>
13 <input
14 value={search}
15 onChange={(e) => setSearch(e.target.value)}
16 placeholder="Search table..."
17 />
18
19 {isSearching && <span className="searching">Searching...</span>}
20
21 <TableBody
22 rows={rows}
23 search={deferredSearch}
24 sortColumn={sortColumn}
25 sortDirection={sortDirection}
26 />
27 </div>
28 );
29}
30
31const TableBody = memo(function TableBody({
32 rows,
33 search,
34 sortColumn,
35 sortDirection,
36}) {
37 const processedRows = useMemo(() => {
38 let result = rows;
39
40 // Filter
41 if (search) {
42 result = result.filter((row) =>
43 Object.values(row).some((value) =>
44 String(value).toLowerCase().includes(search.toLowerCase())
45 )
46 );
47 }
48
49 // Sort
50 result = [...result].sort((a, b) => {
51 const aVal = a[sortColumn];
52 const bVal = b[sortColumn];
53 const direction = sortDirection === 'asc' ? 1 : -1;
54 return aVal > bVal ? direction : -direction;
55 });
56
57 return result;
58 }, [rows, search, sortColumn, sortDirection]);
59
60 return (
61 <tbody>
62 {processedRows.map((row) => (
63 <tr key={row.id}>
64 {Object.values(row).map((cell, i) => (
65 <td key={i}>{cell}</td>
66 ))}
67 </tr>
68 ))}
69 </tbody>
70 );
71});vs useTransition#
1import { useState, useTransition, useDeferredValue } from 'react';
2
3// useTransition - you control when to defer
4function WithTransition() {
5 const [query, setQuery] = useState('');
6 const [isPending, startTransition] = useTransition();
7
8 const handleChange = (e) => {
9 // Input updates immediately
10 setQuery(e.target.value);
11
12 // Results update in transition
13 startTransition(() => {
14 setQuery(e.target.value);
15 });
16 };
17
18 return (
19 <div>
20 <input onChange={handleChange} />
21 <Results query={query} />
22 </div>
23 );
24}
25
26// useDeferredValue - React controls deferral
27function WithDeferredValue() {
28 const [query, setQuery] = useState('');
29 const deferredQuery = useDeferredValue(query);
30
31 // React automatically defers based on value
32 return (
33 <div>
34 <input
35 value={query}
36 onChange={(e) => setQuery(e.target.value)}
37 />
38 <Results query={deferredQuery} />
39 </div>
40 );
41}
42
43// Key difference:
44// - useTransition: wrap the state update
45// - useDeferredValue: wrap the value usageDebouncing Alternative#
1// Traditional debouncing
2function DebounceSearch() {
3 const [query, setQuery] = useState('');
4 const [debouncedQuery, setDebouncedQuery] = useState('');
5
6 useEffect(() => {
7 const timer = setTimeout(() => {
8 setDebouncedQuery(query);
9 }, 300);
10
11 return () => clearTimeout(timer);
12 }, [query]);
13
14 return <Results query={debouncedQuery} />;
15}
16
17// useDeferredValue - no fixed delay
18function DeferredSearch() {
19 const [query, setQuery] = useState('');
20 const deferredQuery = useDeferredValue(query);
21
22 // Defers based on device capability
23 // Fast devices: minimal delay
24 // Slow devices: longer delay to keep responsive
25
26 return <Results query={deferredQuery} />;
27}
28
29// Combine both for best results
30function OptimalSearch() {
31 const [query, setQuery] = useState('');
32 const deferredQuery = useDeferredValue(query);
33
34 // Debounce network requests
35 // Defer expensive renders
36 useEffect(() => {
37 if (!deferredQuery) return;
38
39 const timer = setTimeout(() => {
40 fetchResults(deferredQuery);
41 }, 300);
42
43 return () => clearTimeout(timer);
44 }, [deferredQuery]);
45
46 return <Results query={deferredQuery} />;
47}Best Practices#
Usage:
✓ Defer expensive renders
✓ Keep input responsive
✓ Show stale indicators
✓ Memoize child components
When to Use:
✓ Filtering large lists
✓ Complex visualizations
✓ Real-time search
✓ Expensive computations
Patterns:
✓ Combine with memo()
✓ Track stale state
✓ Use with Suspense
✓ Apply visual feedback
Avoid:
✗ Deferring fast operations
✗ Without memoization
✗ For network requests alone
✗ Nested deferred values
Conclusion#
The useDeferredValue hook improves UI responsiveness by deferring non-urgent updates. Use it for expensive renders like filtering large lists or rendering complex charts. Always memoize child components and provide visual feedback when showing stale content. It adapts to device capabilities automatically, unlike fixed debounce delays.