useTransition & Concurrent Lanes: Priority Scheduling & Non-Blocking Updates
- Last updated
- Prerequisites:
- React Fiber Reconciler
- State updates with useState
- react
- useTransition
- startTransition
- concurrent
- lanes
- scheduler
Read at your depth
The practical view
`useTransition` is a hook that marks a state update as a non-urgent 'transition'. `const [isPending, startTransition] = useTransition();`. Inside `startTransition(() => { setHeavyState(data); })`, React deprioritizes the update. Urgent updates (like typing in an input or clicking a toggle) can interrupt the transition render, keeping the input responsive. `isPending` tells you if the background transition is currently rendering.
The same idea in other frameworks
`useTransition` / `startTransition` assigning `TransitionLane` priority.Zoneless signal updates with manual priority deferral via `scheduler.postTask`.Vue 3 does not have concurrent lanes; it uses macro/microtask batching.Svelte 5 compiles direct updates with transition timing via microtask scheduling.Legacy vs modern
Blocking Input State vs Non-Blocking useTransition
Synchronous state updates for large lists freeze input typing; `useTransition` keeps keystrokes instant while deferring the list update.
setQuery(val); setList(filter(val)); // ❌ Freezes typing on large listsstartTransition(() => { setFiltered(filter(val)); }); // ✅ NEW: Non-blockingInterview gotchas
Context
Tests knowledge of React 18 concurrent primitive specialization.
The mechanical answer
`useTransition` is used when you have direct control over the state-setting function (`startTransition(() => setState(...))`). `useDeferredValue` is used when you receive a value from props or an external hook without direct access to the state setter (`const deferredValue = useDeferredValue(props.value)`). Both use the same underlying Transition Lanes priority mechanism.
Trap
Do not wrap `useDeferredValue` inside `startTransition`. They are complementary approaches for different code locations.
Context
Checks knowledge of React 19 Action transitions.
The mechanical answer
In React 18, `startTransition` only accepted synchronous callbacks (state updates after an `await` were not tracked by `isPending`). In React 19, `startTransition` supports async functions (Async Actions): `startTransition(async () => { await api(); setState(); })`. `isPending` remains `true` across the entire `await` lifecycle.
Trap
In React 18, wrapping an `async` function inside `startTransition` caused `isPending` to flip back to `false` on the first microtask boundary before the promise resolved.