transitions

useTransition

Marks state updates inside startTransition as non-urgent so React can keep the UI responsive to more urgent updates. Reach for it when a state change triggers heavy rendering (search results, tab switches) and you want the previous UI to stay interactive.

const [isPending, startTransition] = useTransition()

Parameters & Behavior

Parameter Purpose
isPending Boolean — true while the transition is in flight; useful for spinners
startTransition(fn) Runs fn synchronously; state updates inside it are flagged as transitions
concurrent rendering React can interrupt the transition to service urgent updates (typing, clicks)
no async allowed In classic useTransition, startTransition's callback must be synchronous

Examples

const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const onChange = (e) => {
  setQuery(e.target.value);
  startTransition(() => setResults(search(e.target.value)));
};

Keep the input responsive while the expensive results list re-renders

startTransition(() => setTab(nextTab));
return isPending ? <Spinner /> : <TabContent tab={tab} />;

Show a spinner during a heavy tab switch without blocking clicks

startTransition(() => setFilters(nextFilters));

Filter updates over a large dataset — marked non-urgent so scrolling stays smooth

<button disabled={isPending} onClick={() => startTransition(() => setPage(p + 1))}>Next</button>

Disable the button while the transition renders the next page

Gotcha

Only wrap state updates that trigger the expensive rendering — updates outside startTransition stay urgent and can cause tearing. The synchronous startTransition cannot await; use React 19's async actions or startTransition from `react` (not the hook) for those cases.

Related hooks

← All React hooks · Fix hydration mismatch