facebook/reactMITd083ec1Report / request removal

Concurrent Features & Transitions

startTransition marks work by placing a transition object in ReactSharedInternals.T while its callback runs. Updates created inside that callback can then be associated with a transition lane instead of the ordinary event-priority lane.

This exists so React can keep transition work from blocking more urgent work. The render can remain in progress when a lower-priority update arrives, while a sufficiently urgent lane can replace it; transition tracing also gives React a stack-based way to report which transitions are pending.

Sources: packages/react/src/ReactStartTransition.js:45-76, packages/react-reconciler/src/ReactFiberWorkLoop.js:845-867, packages/react-reconciler/src/ReactFiberLane.js:346-357, packages/react-reconciler/src/ReactFiberTransition.js:256-265, packages/react-reconciler/src/ReactFiberTransition.js:267-275, packages/react-reconciler/src/ReactFiberTransition.js:313-319

Core concepts

Transition scope

A transition scope is the callback passed to startTransition; React installs a fresh currentTransition before calling it and restores the previous transition afterward.

Sources: packages/react/src/ReactStartTransition.js:45-76, packages/react/src/ReactStartTransition.js:94-117

Current transition

The current transition is the object stored in ReactSharedInternals.T, which the reconciler reads through requestCurrentTransition.

Sources: packages/react/src/ReactStartTransition.js:49-50, packages/react/src/ReactStartTransition.js:73-76, packages/react-reconciler/src/ReactFiberTransition.js:193-195

Transition lane

A transition lane is the lane requested when an update sees a non-null transition; ordinary updates instead use the lane derived from the current event priority.

Sources: packages/react-reconciler/src/ReactFiberWorkLoop.js:845-867

Pending transition stack

The pending transition stack records transition arrays while transition tracing is enabled; getPendingTransitions returns its current value and otherwise returns null.

Sources: packages/react-reconciler/src/ReactFiberTransition.js:206-207, packages/react-reconciler/src/ReactFiberTransition.js:313-319

How startTransition changes an update

When code calls startTransition, React saves the previous transition and creates a new transition object. Depending on enabled features, the object may receive view-transition types, a gesture field, tracing metadata, and a development-only _updatedFibers set. React then assigns it to ReactSharedInternals.T before invoking the scope.

The important change for state updates is not that the callback immediately changes state differently. It is that updates created while the callback runs can observe the current transition. The reconciler records the updated fiber in development and calls requestTransitionLane(transition) when a transition is present; without one, it resolves the event priority and calls eventPriorityToLane(priority).

const prevTransition = ReactSharedInternals.T;
const currentTransition: Transition = {} as any;
ReactSharedInternals.T = currentTransition;

try {
  const returnValue = scope();
  const onStartTransitionFinish = ReactSharedInternals.S;
  if (onStartTransitionFinish !== null) {
    onStartTransitionFinish(currentTransition, returnValue);
  }
} finally {
  ReactSharedInternals.T = prevTransition;
}

The callback runs while the new transition is current, and the previous transition is restored in finally, so nested or surrounding transition context is not left installed accidentally.

This call order shows how the public API hands control to application code and then reports completion.

Starting a transition — How does startTransition mark work before the callback runs?

Evidence

If the scope returns a thenable, development builds increment ReactSharedInternals.asyncTransitions and attach releaseAsyncTransition to both fulfillment and rejection; the result is also connected to reportGlobalError for rejection handling.

Sources: packages/react/src/ReactStartTransition.js:49-76, packages/react-reconciler/src/ReactFiberWorkLoop.js:845-867, packages/react/src/ReactStartTransition.js:49-50, packages/react/src/ReactStartTransition.js:73-80, packages/react/src/ReactStartTransition.js:94-117, packages/react/src/ReactStartTransition.js:81-93

Why transition work can be interrupted

React compares the next lane with the lane currently being rendered. If the next lane is equal to or lower priority than the work-in-progress lane, React keeps working on the existing tree. The shown special case also prevents a DefaultLane update from interrupting work in TransitionLanes.

Therefore, transition work is interruptible because lane selection is independent from the render already in progress: a lane that does not satisfy the “keep working” condition can take precedence, while a default-priority update is explicitly prevented from interrupting transition lanes. This is why a more urgent update can be handled ahead of unfinished transition work.

Choosing whether to interrupt — Why can urgent work take precedence over a transition render?

Evidence

The update queue preserves skipped lower-priority work rather than discarding it. When an update lacks sufficient priority, it remains queued for a later lower-priority render, and subsequent updates remain queued as well.

Sources: packages/react-reconciler/src/ReactFiberLane.js:346-357, packages/react-reconciler/src/ReactFiberClassUpdateQueue.js:48-58

How React knows a transition is pending

For transition tracing, transitionStack is a cursor containing either an array of transitions or null. Root work pushes the root’s transitions with pushRootTransition, and offscreen work pushes either resumed or newly combined transition state with pushTransition.

When the corresponding work completes, popRootTransition and popTransition remove the stack entries. popTransition also removes the resumed cache entry when a current fiber exists.

getPendingTransitions exposes the current stack value only when enableTransitionTracing is enabled. Thus, in the code shown, “pending” means the transitions currently represented by the tracing stack; when tracing is disabled, the accessor deliberately reports null.

A suspended render can preserve the cache selected for fresh data. getSuspendedCache calls peekCacheFromPool and returns the cache together with its parent context, allowing resumed rendering to use the same pool; getOffscreenDeferredCache follows the same cache-pool pattern for deferred offscreen work.

Sources: packages/react-reconciler/src/ReactFiberTransition.js:206-207, packages/react-reconciler/src/ReactFiberTransition.js:256-265, packages/react-reconciler/src/ReactFiberTransition.js:277-301, packages/react-reconciler/src/ReactFiberTransition.js:267-275, packages/react-reconciler/src/ReactFiberTransition.js:303-311, packages/react-reconciler/src/ReactFiberTransition.js:313-319, packages/react-reconciler/src/ReactFiberTransition.js:321-340, packages/react-reconciler/src/ReactFiberTransition.js:342-357

How it connects

The public startTransition API belongs to the React package, while transition context and lane selection are consumed by the reconciler. ReactSharedInternals.T is the hand-off between those layers: the API writes it, and requestCurrentTransition reads it.

For repository orientation, start with React Overview and Repository Map The scheduling consequences belong with Lanes & Priority and Work Loop & Scheduling The update queues are covered by useState & useReducer

Sources: packages/react/src/ReactStartTransition.js:49-76, packages/react-reconciler/src/ReactFiberTransition.js:193-195, packages/react-reconciler/src/ReactFiberWorkLoop.js:845-867

Key takeaways

  • startTransition installs a transition object while its scope runs.
  • Updates inside that scope request a transition lane instead of deriving a lane directly from event priority.
  • Lane comparison lets more urgent work take precedence while protecting transition work from default-priority interruption.
  • Transition tracing tracks pending transitions through transitionStack.
  • Suspended transition renders can preserve and later reuse their cache pool.

Want this for your repos?

Try Angada AI Wiki