Open-Source Wikis

/

React

/

Features

/

Concurrent rendering

facebook/react

Concurrent rendering

"Concurrent React" is the name for the cluster of features that all rely on the lane-based, time-sliced work loop in react-reconciler: transitions, deferred values, Suspense for data, automatic batching, selective hydration, view transitions. This page traces how they cooperate at runtime.

The core idea

A render in concurrent React is interruptible. Between any two fibers in the work loop, the reconciler can call shouldYield() from the scheduler, hand control back to the browser to paint or process input, and resume later — possibly at a higher priority. The machinery is:

  • A 31-bit bitmask of lanes prioritizing pending work (packages/react-reconciler/src/ReactFiberLane.js).
  • A cooperative scheduler that owns the deadline (packages/scheduler/src/Scheduler.js).
  • A work loop that tracks the in-flight render and can suspend mid-tree (packages/react-reconciler/src/ReactFiberWorkLoop.js).

Lanes

A lane is one bit. A lane set is a 31-bit mask. The bit positions encode priority — lower-numbered bits are higher priority. The full list (in declaration order) is in ReactFiberLane.js:

SyncHydrationLane
SyncLane
InputContinuousHydrationLane
InputContinuousLane
DefaultHydrationLane
DefaultLane
TransitionHydrationLane
TransitionLane1 .. TransitionLane14
RetryLane1 .. RetryLane4
SelectiveHydrationLane
IdleHydrationLane
IdleLane
OffscreenLane
DeferredLane
GestureLane

A handful of helpers do all the work:

  • getNextLanes(root, wipLanes) — picks which lanes to render next, given what's pending and what's currently rendering.
  • pickArbitraryLane(lanes)lanes & -lanes, isolates the lowest set bit. Used to single out the highest-priority lane in a set.
  • getLanesToRetrySynchronouslyOnError, markRootSuspended, markRootEntangled, etc. — bookkeeping on the FiberRoot.

packages/react-reconciler/src/ReactEventPriorities.js maps DOM events to lanes:

  • A click → DiscreteEventPrioritySyncLane.
  • A scroll, mouse-move → ContinuousEventPriorityInputContinuousLane.
  • An async fetch's resolution → DefaultEventPriorityDefaultLane.

startTransition(fn) (packages/react/src/ReactStartTransition.js) sets a per-fiber transition flag; any setState inside fn picks a TransitionLane* lane instead.

The work loop

Two entry points in ReactFiberWorkLoop.js:

Entry When Loop behavior
performSyncWorkOnRoot Sync lanes (default discrete events) Uninterruptible. Renders the entire tree, then commits.
performConcurrentWorkOnRoot Anything else Calls shouldYield() between fibers; can pause and resume.

workLoopSync and workLoopConcurrent are the inner loops (just while (workInProgress !== null) ± a yield check). They each call performUnitOfWork per fiber, which calls beginWork → may spawn children → returns the next fiber.

graph TD
  Update[setState / dispatch / root.render] --> Sched[ensureRootIsScheduled]
  Sched -->|pick lane| ScheduleCallback[scheduleCallback NormalPriority/UserBlocking]
  ScheduleCallback -->|next tick| WorkLoop[performConcurrentWorkOnRoot]
  WorkLoop -->|prepare WIP root| RenderRoot[renderRootConcurrent]
  RenderRoot -->|workLoopConcurrent| BeginWork[beginWork]
  BeginWork -->|child / fallback / suspend| RenderRoot
  RenderRoot -->|shouldYield true| Yield[return RootInProgress]
  Yield --> ScheduleCallback
  RenderRoot -->|done| CommitRoot[commitRoot]
  CommitRoot -->|host mutations + effects| DOM[(DOM)]

After a yield, the next tick's scheduleCallback will resume the same root via performConcurrentWorkOnRoot. If a higher-priority update came in during the yield (e.g. a click), the reconciler may throw away the WIP tree and start a fresh render at the higher priority; the original work resumes when the high-priority pass settles.

Transitions

startTransition(fn):

function startTransition(scope) {
  const prevTransition = ReactSharedInternals.T;
  ReactSharedInternals.T = {
    /* a Transition object */
  };
  try {
    scope();
  } finally {
    ReactSharedInternals.T = prevTransition;
  }
}

ReactSharedInternals.T is the current transition. Inside dispatchSetStateInternal, the lane is chosen via requestUpdateLane(fiber), which checks T and returns a TransitionLane* lane if set. This is how any setState made synchronously inside fn becomes a transition.

useTransition() is a wrapper that exposes isPending (a state that toggles to true while the transition is in flight) and a startTransition bound to that state.

useDeferredValue(value) is similar: it commits two updates — a default-priority one with the previous value, and a transition-priority one with the new value. The default-priority one renders immediately; the transition fills in later.

Entanglement

Lanes can be entangled — when one lane is committed, all lanes it's entangled with must be committed in the same pass. This prevents tearing in cases like:

  • Two transitions targeting overlapping state need to commit atomically.
  • A retry of a suspended boundary must be entangled with the lane that originally rendered it.

packages/react-reconciler/src/ReactFiberLane.js has markRootEntangled and friends; the rules are dense and best read directly.

Selective hydration

packages/react-reconciler/src/ReactFiberHydrationContext.js (and the matching DOM/server code) handles hydrating SSR HTML. With Suspense and streaming SSR (Fizz), boundaries arrive over time. The reconciler uses lanes to:

  • Hydrate boundaries in the order they stream in, low priority by default.
  • Bump priority of a boundary the user clicks or interacts with (the click is SyncLane; the boundary becomes SyncHydrationLane).
  • Defer hydration entirely for boundaries the user hasn't reached yet.

This is "selective hydration" — the user's interactions drive what gets hydrated first.

View transitions and gestures

<ViewTransition> (in packages/react-reconciler/src/ReactFiberViewTransitionComponent.js) wraps a subtree and asks the browser's View Transitions API to animate between commits. The commit-phase code is in ReactFiberCommitViewTransitions.js.

useSwipeTransition and startGestureTransition add a second scheduling axis on top of lanes — a gesture can be in flight independently of any committed render, and ReactFiberGestureScheduler.js plus ReactFiberApplyGesture.js orchestrate how gesture frames are interpolated against the committed tree. These are recent (2025/2026) additions and are still landing under flags.

Activity (formerly Offscreen)

<Activity mode="visible|hidden"> (publicly) / OffscreenComponent (internally) — a primitive that hides a subtree without unmounting it. Implementation in packages/react-reconciler/src/ReactFiberOffscreenComponent.js. Hidden subtrees:

  • Have their effects torn down (so e.g. a useEffect subscription unsubscribes) but their state preserved.
  • Use the special OffscreenLane so they don't compete with the visible tree for normal-priority bandwidth.
  • Can be re-revealed instantly without rerunning expensive renders.

This is the mechanism that backs route-level state preservation in modern frameworks.

Where to look

  • The work-loop top-level: packages/react-reconciler/src/ReactFiberWorkLoop.js. It's long but well-commented.
  • The lane bitmask logic: packages/react-reconciler/src/ReactFiberLane.js.
  • The scheduler: packages/scheduler/src/Scheduler.js. Tiny.
  • The transition primitive: packages/react/src/ReactStartTransition.js and packages/react-reconciler/src/ReactFiberTransition.js.
  • View transitions: packages/react-reconciler/src/ReactFiberCommitViewTransitions.js, ReactFiberApplyGesture.js, ReactFiberGestureScheduler.js.
  • Activity: packages/react-reconciler/src/ReactFiberOffscreenComponent.js.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Concurrent rendering – React wiki | Factory