Concurrent React: useTransition and useDeferredValue
React 18 introduced Concurrent Mode, changing how React processes UI updates. Previously, rendering in React was an uninterrupted, synchronous process. If a large render started, the main thread was blocked, preventing user interactions like typing or clicking.
Concurrent features like useTransition and useDeferredValue allow React to interrupt rendering to handle urgent events (like user input) before returning to less urgent tasks (like rendering a large list or fetching data).
1. useTransition: Marking State as Non-Urgent
useTransition lets you distinguish between urgent updates (like typing in an input) and non-urgent updates (like filtering a heavy list).
Usage
import { useState, useTransition } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// Urgent update: The input should update instantly
setQuery(e.target.value);
// Non-urgent update: Searching the database/filtering
startTransition(() => {
const filtered = performHeavySearch(e.target.value);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? <p>Loading results...</p> : <List data={results} />}
</div>
);
}
Key Concepts:
isPending: A boolean that istruewhile the transition is happening. Great for showing a loading spinner.startTransition: A function that wraps the state update you want to deprioritize.
2. useDeferredValue: Delaying a Value
While useTransition is used for callbacks and state setters, useDeferredValue is used for props and state values. It creates a deferred version of a value that lags behind the original until the main thread is free.
This is highly useful when you receive a value from a parent component and want to defer rendering a heavy child component based on it.
Usage
import { useState, useDeferredValue, memo } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<SlowList query={query} />
</>
);
}
const SlowList = memo(({ query }) => {
// Defer the query. If 'query' changes rapidly, 'deferredQuery'
// will wait until the CPU is idle to catch up.
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{/* Heavy rendering based on deferredQuery */}
<HeavyComponent data={deferredQuery} />
</div>
);
});
Key Concepts:
- Under the hood,
useDeferredValueis similar to debouncing or throttling, but it's deeply integrated into React's event loop. It doesn't rely on arbitrary timer values likesetTimeout; it reacts instantly when the CPU is free.
Summary: Which one to use?
| Feature | When to use | Target |
|---------|-------------|--------|
| useTransition | When you have control over the state update (you have access to setState). | Functions / State Setters |
| useDeferredValue | When you receive a value from props or top-level state and cannot control the setter. | Values / Props |
Both hooks solve the same core problem: preventing slow renders from blocking the user interface. Use them sparingly, primarily when you have proven performance bottlenecks in rendering large component trees or complex data visualizations.