5 min read
0%

startTransition and useTransition

Back to Blog
startTransition and useTransition

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. startTransition does 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.


Canvas is not supported in your browser