
startTransition and useTransition
startTransition lets you mark state updates as non-urgent. React renders them in the background and won’t let them block user input.
The Problem
Expensive state updates block the UI:
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
setQuery(e.target.value);
setResults(filterHeavyDataset(e.target.value)); // slow
}
return <input onChange={handleChange} value={query} />;
} Every keystroke triggers filterHeavyDataset, which blocks rendering. The input feels laggy.
startTransition
Wrap the slow update:
import { startTransition } from 'react';
function handleChange(e) {
setQuery(e.target.value); // urgent — update immediately
startTransition(() => {
setResults(filterHeavy(e.target.value)); // non-urgent
});
} React renders the query update synchronously (fast). The results update is deferred and can be interrupted if the user types again.
useTransition
useTransition is the hook version, which additionally provides an isPending flag:
const [isPending, startTransition] = useTransition();
function handleChange(e) {
setQuery(e.target.value);
startTransition(() => {
setResults(filterHeavy(e.target.value));
});
}
return (
<>
<input onChange={handleChange} value={query} />
{isPending && <Spinner />}
<ResultsList results={results} />
</>
); isPending is true while the transition render is in progress — useful for showing a loading indicator without losing the current UI.
What Transitions Are Not
- Not async.
startTransitiondoes not defer the computation — the state update runs synchronously, but React can deprioritize rendering it. - Not a replacement for debouncing. Debounce prevents the update from being scheduled; transitions let it be scheduled but at lower priority.
- Not for truly async operations. For data fetching, combine with Suspense.
Concurrent Requirement
Transitions only work with createRoot (concurrent mode). In legacy ReactDOM.render() roots, startTransition behaves like a normal state update.
useDeferredValue
The value-level version of transitions:
const deferredResults = useDeferredValue(results); deferredResults lags behind results when updates are pending. Useful when you can’t restructure state to use useTransition.









