React 101 - useDeferredValue
In React, we often encounter scenarios where expensive processing can slow down renders.
To address this, React provides the useDeferredValue hook.
This hook allows us to mark resource-intensive tasks as "low priority," ensuring that more urgent UI updates can proceed smoothly without interruption.
Let’s take a look at an example to understand how this works in practice.
In this example, we have a classic Typeahead component that queries a list of cities from a JSON file.
import React, { useState } from 'react';
import cities from './cities.json';
export function Typeahead() {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const filterCities = (input) => {
if (!input) return [];
const start = performance.now();
while (performance.now() - start < 300) {}
return cities.filter((city) =>
city.name.toLowerCase().includes(input.toLowerCase())
);
};
React.useEffect(() => {
const results = filterCities(query);
setSuggestions(results);
}, [query]);
const handleChange = (event) => {
setQuery(event.target.value);
};
return (
<div>
<input
type='text'
value={query}
onChange={handleChange}
placeholder='Search for cities...'
/>
<ul>
{suggestions.map((city, index) => (
<li key={index}>
<strong>{city.name}</strong>
<br />
<small>{city.country}</small>
</li>
))}
</il>
</div>
);
}
You'll notice this section of the code
const start = performance.now();
while (performance.now() - start < 300) {}
Here, we’re simulating a computationally intensive operation that could slow down rendering and create a janky user experience.
The result is a sluggish and choppy experience, as both the suggestions list rendering and the input field updates happen simultaneously. When the filtering process is resource-intensive, it blocks the main thread, causing delays in the UI and unresponsiveness during typing.
In order to fix this, we can utilize the useDeferredValue hook.
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const deferredQuery = useDeferredValue(query);
....
React.useEffect(() => {
const results = filterCities(deferredQuery);
setSuggestions(results);
}, [deferredQuery]);
This marks the deferred query as low-priority, telling React to prioritize more urgent updates (like user interactions) and defer the update of this part of the UI until after higher-priority tasks are completed. This helps prevent blocking the main thread and ensures smoother, more responsive rendering during fast updates.
The end result is a much smoother user experience, as the input field can render without being blocked by the suggestions list.