packages/react-reconciler/src/ReactFiberRootScheduler.js JAVASCRIPT 739 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {FiberRoot} from './ReactInternalTypes';11import type {Lane, Lanes} from './ReactFiberLane';12import type {PriorityLevel} from 'scheduler/src/SchedulerPriorities';13import type {Transition} from 'react/src/ReactStartTransition';1415import {16  disableLegacyMode,17  disableSchedulerTimeoutInWorkLoop,18  enableProfilerTimer,19  enableProfilerNestedUpdatePhase,20  enableComponentPerformanceTrack,21  enableYieldingBeforePassive,22  enableGestureTransition,23  enableDefaultTransitionIndicator,24} from 'shared/ReactFeatureFlags';25import {26  NoLane,27  NoLanes,28  SyncLane,29  DefaultLane,30  getHighestPriorityLane,31  getNextLanes,32  includesSyncLane,33  markStarvedLanesAsExpired,34  claimNextTransitionUpdateLane,35  getNextLanesToFlushSync,36  checkIfRootIsPrerendering,37  isGestureRender,38} from './ReactFiberLane';39import {40  CommitContext,41  NoContext,42  RenderContext,43  flushPendingEffects,44  flushPendingEffectsDelayed,45  getExecutionContext,46  getWorkInProgressRoot,47  getWorkInProgressRootRenderLanes,48  getRootWithPendingPassiveEffects,49  getPendingPassiveEffectsLanes,50  hasPendingCommitEffects,51  isWorkLoopSuspendedOnData,52  performWorkOnRoot,53} from './ReactFiberWorkLoop';54import {LegacyRoot} from './ReactRootTags';55import {56  ImmediatePriority as ImmediateSchedulerPriority,57  UserBlockingPriority as UserBlockingSchedulerPriority,58  NormalPriority as NormalSchedulerPriority,59  IdlePriority as IdleSchedulerPriority,60  cancelCallback as Scheduler_cancelCallback,61  scheduleCallback as Scheduler_scheduleCallback,62  now,63} from './Scheduler';64import {65  DiscreteEventPriority,66  ContinuousEventPriority,67  DefaultEventPriority,68  IdleEventPriority,69  lanesToEventPriority,70} from './ReactEventPriorities';71import {72  supportsMicrotasks,73  scheduleMicrotask,74  shouldAttemptEagerTransition,75  trackSchedulerEvent,76  noTimeout,77} from './ReactFiberConfig';7879import ReactSharedInternals from 'shared/ReactSharedInternals';80import {81  resetNestedUpdateFlag,82  syncNestedUpdateFlag,83} from './ReactProfilerTimer';84import {peekEntangledActionLane} from './ReactFiberAsyncAction';8586import noop from 'shared/noop';87import reportGlobalError from 'shared/reportGlobalError';8889import {90  startIsomorphicDefaultIndicatorIfNeeded,91  hasOngoingIsomorphicIndicator,92  retainIsomorphicIndicator,93  markIsomorphicIndicatorHandled,94} from './ReactFiberAsyncAction';9596// A linked list of all the roots with pending work. In an idiomatic app,97// there's only a single root, but we do support multi root apps, hence this98// extra complexity. But this module is optimized for the single root case.99export let firstScheduledRoot: FiberRoot | null = null;100let lastScheduledRoot: FiberRoot | null = null;101102// Used to prevent redundant mircotasks from being scheduled.103let didScheduleMicrotask: boolean = false;104// `act` "microtasks" are scheduled on the `act` queue instead of an actual105// microtask, so we have to dedupe those separately. This wouldn't be an issue106// if we required all `act` calls to be awaited, which we might in the future.107let didScheduleMicrotask_act: boolean = false;108109// Used to quickly bail out of flushSync if there's no sync work to do.110let mightHavePendingSyncWork: boolean = false;111112let isFlushingWork: boolean = false;113114let currentEventTransitionLane: Lane = NoLane;115116export function ensureRootIsScheduled(root: FiberRoot): void {117  // This function is called whenever a root receives an update. It does two118  // things 1) it ensures the root is in the root schedule, and 2) it ensures119  // there's a pending microtask to process the root schedule.120  //121  // Most of the actual scheduling logic does not happen until122  // `scheduleTaskForRootDuringMicrotask` runs.123124  // Add the root to the schedule125  if (root === lastScheduledRoot || root.next !== null) {126    // Fast path. This root is already scheduled.127  } else {128    if (lastScheduledRoot === null) {129      firstScheduledRoot = lastScheduledRoot = root;130    } else {131      lastScheduledRoot.next = root;132      lastScheduledRoot = root;133    }134  }135136  // Any time a root received an update, we set this to true until the next time137  // we process the schedule. If it's false, then we can quickly exit flushSync138  // without consulting the schedule.139  mightHavePendingSyncWork = true;140141  ensureScheduleIsScheduled();142143  if (144    __DEV__ &&145    !disableLegacyMode &&146    ReactSharedInternals.isBatchingLegacy &&147    root.tag === LegacyRoot148  ) {149    // Special `act` case: Record whenever a legacy update is scheduled.150    ReactSharedInternals.didScheduleLegacyUpdate = true;151  }152}153154export function ensureScheduleIsScheduled(): void {155  // At the end of the current event, go through each of the roots and ensure156  // there's a task scheduled for each one at the correct priority.157  if (__DEV__ && ReactSharedInternals.actQueue !== null) {158    // We're inside an `act` scope.159    if (!didScheduleMicrotask_act) {160      didScheduleMicrotask_act = true;161      scheduleImmediateRootScheduleTask();162    }163  } else {164    if (!didScheduleMicrotask) {165      didScheduleMicrotask = true;166      scheduleImmediateRootScheduleTask();167    }168  }169}170171export function flushSyncWorkOnAllRoots() {172  // This is allowed to be called synchronously, but the caller should check173  // the execution context first.174  flushSyncWorkAcrossRoots_impl(NoLanes, false);175}176177export function flushSyncWorkOnLegacyRootsOnly() {178  // This is allowed to be called synchronously, but the caller should check179  // the execution context first.180  if (!disableLegacyMode) {181    flushSyncWorkAcrossRoots_impl(NoLanes, true);182  }183}184185function flushSyncWorkAcrossRoots_impl(186  syncTransitionLanes: Lanes | Lane,187  onlyLegacy: boolean,188) {189  if (isFlushingWork) {190    // Prevent reentrancy.191    // TODO: Is this overly defensive? The callers must check the execution192    // context first regardless.193    return;194  }195196  if (!mightHavePendingSyncWork) {197    // Fast path. There's no sync work to do.198    return;199  }200201  // There may or may not be synchronous work scheduled. Let's check.202  let didPerformSomeWork;203  isFlushingWork = true;204  do {205    didPerformSomeWork = false;206    let root = firstScheduledRoot;207    while (root !== null) {208      if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {209        // Skip non-legacy roots.210      } else {211        if (syncTransitionLanes !== NoLanes) {212          const nextLanes = getNextLanesToFlushSync(root, syncTransitionLanes);213          if (nextLanes !== NoLanes) {214            // This root has pending sync work. Flush it now.215            didPerformSomeWork = true;216            performSyncWorkOnRoot(root, nextLanes);217          }218        } else {219          const workInProgressRoot = getWorkInProgressRoot();220          const workInProgressRootRenderLanes =221            getWorkInProgressRootRenderLanes();222          const rootHasPendingCommit =223            root.cancelPendingCommit !== null ||224            root.timeoutHandle !== noTimeout;225          const nextLanes = getNextLanes(226            root,227            root === workInProgressRoot228              ? workInProgressRootRenderLanes229              : NoLanes,230            rootHasPendingCommit,231          );232          if (233            (includesSyncLane(nextLanes) ||234              (enableGestureTransition && isGestureRender(nextLanes))) &&235            !checkIfRootIsPrerendering(root, nextLanes)236          ) {237            // This root has pending sync work. Flush it now.238            didPerformSomeWork = true;239            performSyncWorkOnRoot(root, nextLanes);240          }241        }242      }243      root = root.next;244    }245  } while (didPerformSomeWork);246  isFlushingWork = false;247}248249function processRootScheduleInImmediateTask() {250  if (enableProfilerTimer && enableComponentPerformanceTrack) {251    // Track the currently executing event if there is one so we can ignore this252    // event when logging events.253    trackSchedulerEvent();254  }255256  processRootScheduleInMicrotask();257}258259function processRootScheduleInMicrotask() {260  // This function is always called inside a microtask. It should never be261  // called synchronously.262  didScheduleMicrotask = false;263  if (__DEV__) {264    didScheduleMicrotask_act = false;265  }266267  // We'll recompute this as we iterate through all the roots and schedule them.268  mightHavePendingSyncWork = false;269270  let syncTransitionLanes = NoLanes;271  if (currentEventTransitionLane !== NoLane) {272    if (shouldAttemptEagerTransition()) {273      // A transition was scheduled during an event, but we're going to try to274      // render it synchronously anyway. We do this during a popstate event to275      // preserve the scroll position of the previous page.276      syncTransitionLanes = currentEventTransitionLane;277    } else if (enableDefaultTransitionIndicator) {278      // If we have a Transition scheduled by this event it might be paired279      // with Default lane scheduled loading indicators. To unbatch it from280      // other events later on, flush it early to determine whether it281      // rendered an indicator. This ensures that setState in default priority282      // event doesn't trigger onDefaultTransitionIndicator.283      syncTransitionLanes = DefaultLane;284    }285  }286287  const currentTime = now();288289  let prev = null;290  let root = firstScheduledRoot;291  while (root !== null) {292    const next = root.next;293    const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);294    if (nextLanes === NoLane) {295      // This root has no more pending work. Remove it from the schedule. To296      // guard against subtle reentrancy bugs, this microtask is the only place297      // we do this — you can add roots to the schedule whenever, but you can298      // only remove them here.299300      // Null this out so we know it's been removed from the schedule.301      root.next = null;302      if (prev === null) {303        // This is the new head of the list304        firstScheduledRoot = next;305      } else {306        prev.next = next;307      }308      if (next === null) {309        // This is the new tail of the list310        lastScheduledRoot = prev;311      }312    } else {313      // This root still has work. Keep it in the list.314      prev = root;315316      // This is a fast-path optimization to early exit from317      // flushSyncWorkOnAllRoots if we can be certain that there is no remaining318      // synchronous work to perform. Set this to true if there might be sync319      // work left.320      if (321        // Skip the optimization if syncTransitionLanes is set322        syncTransitionLanes !== NoLanes ||323        // Common case: we're not treating any extra lanes as synchronous, so we324        // can just check if the next lanes are sync.325        includesSyncLane(nextLanes) ||326        (enableGestureTransition && isGestureRender(nextLanes))327      ) {328        mightHavePendingSyncWork = true;329      }330    }331    root = next;332  }333334  // At the end of the microtask, flush any pending synchronous work. This has335  // to come at the end, because it does actual rendering work that might throw.336  // If we're in the middle of a View Transition async sequence, we don't want to337  // interrupt that sequence. Instead, we'll flush any remaining work when it338  // completes.339  if (!hasPendingCommitEffects()) {340    flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);341  }342343  if (currentEventTransitionLane !== NoLane) {344    // Reset Event Transition Lane so that we allocate a new one next time.345    currentEventTransitionLane = NoLane;346    startDefaultTransitionIndicatorIfNeeded();347  }348}349350function startDefaultTransitionIndicatorIfNeeded() {351  if (!enableDefaultTransitionIndicator) {352    return;353  }354  // Check if we need to start an isomorphic indicator like if an async action355  // was started.356  startIsomorphicDefaultIndicatorIfNeeded();357  // Check all the roots if there are any new indicators needed.358  let root = firstScheduledRoot;359  while (root !== null) {360    if (root.indicatorLanes !== NoLanes && root.pendingIndicator === null) {361      // We have new indicator lanes that requires a loading state. Start the362      // default transition indicator.363      if (hasOngoingIsomorphicIndicator()) {364        // We already have an isomorphic indicator going which means it has to365        // also apply to this root since it implies all roots have the same one.366        // We retain this indicator so that it keeps going until we commit this367        // root.368        root.pendingIndicator = retainIsomorphicIndicator();369      } else {370        try {371          const onDefaultTransitionIndicator =372            root.onDefaultTransitionIndicator;373          root.pendingIndicator = onDefaultTransitionIndicator() || noop;374        } catch (x) {375          root.pendingIndicator = noop;376          reportGlobalError(x);377        }378      }379    }380    root = root.next;381  }382}383384function scheduleTaskForRootDuringMicrotask(385  root: FiberRoot,386  currentTime: number,387): Lane {388  // This function is always called inside a microtask, or at the very end of a389  // rendering task right before we yield to the main thread. It should never be390  // called synchronously.391392  // This function also never performs React work synchronously; it should393  // only schedule work to be performed later, in a separate task or microtask.394395  // Check if any lanes are being starved by other work. If so, mark them as396  // expired so we know to work on those next.397  markStarvedLanesAsExpired(root, currentTime);398399  // Determine the next lanes to work on, and their priority.400  const rootWithPendingPassiveEffects = getRootWithPendingPassiveEffects();401  const pendingPassiveEffectsLanes = getPendingPassiveEffectsLanes();402  const workInProgressRoot = getWorkInProgressRoot();403  const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();404  const rootHasPendingCommit =405    root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;406  const nextLanes =407    enableYieldingBeforePassive && root === rootWithPendingPassiveEffects408      ? // This will schedule the callback at the priority of the lane but we used to409        // always schedule it at NormalPriority. Discrete will flush it sync anyway.410        // So the only difference is Idle and it doesn't seem necessarily right for that411        // to get upgraded beyond something important just because we're past commit.412        pendingPassiveEffectsLanes413      : getNextLanes(414          root,415          root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,416          rootHasPendingCommit,417        );418419  const existingCallbackNode = root.callbackNode;420  if (421    // Check if there's nothing to work on422    nextLanes === NoLanes ||423    // If this root is currently suspended and waiting for data to resolve, don't424    // schedule a task to render it. We'll either wait for a ping, or wait to425    // receive an update.426    //427    // Suspended render phase428    (root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||429    // Suspended commit phase430    root.cancelPendingCommit !== null431  ) {432    // Fast path: There's nothing to work on.433    if (existingCallbackNode !== null) {434      cancelCallback(existingCallbackNode);435    }436    root.callbackNode = null;437    root.callbackPriority = NoLane;438    return NoLane;439  }440441  // Schedule a new callback in the host environment.442  if (443    includesSyncLane(nextLanes) &&444    // If we're prerendering, then we should use the concurrent work loop445    // even if the lanes are synchronous, so that prerendering never blocks446    // the main thread.447    !checkIfRootIsPrerendering(root, nextLanes)448  ) {449    // Synchronous work is always flushed at the end of the microtask, so we450    // don't need to schedule an additional task.451    if (existingCallbackNode !== null) {452      cancelCallback(existingCallbackNode);453    }454    root.callbackPriority = SyncLane;455    root.callbackNode = null;456    return SyncLane;457  } else {458    // We use the highest priority lane to represent the priority of the callback.459    const existingCallbackPriority = root.callbackPriority;460    const newCallbackPriority = getHighestPriorityLane(nextLanes);461462    if (463      newCallbackPriority === existingCallbackPriority &&464      // Special case related to `act`. If the currently scheduled task is a465      // Scheduler task, rather than an `act` task, cancel it and re-schedule466      // on the `act` queue.467      !(468        __DEV__ &&469        ReactSharedInternals.actQueue !== null &&470        existingCallbackNode !== fakeActCallbackNode471      )472    ) {473      // The priority hasn't changed. We can reuse the existing task.474      return newCallbackPriority;475    } else {476      // Cancel the existing callback. We'll schedule a new one below.477      cancelCallback(existingCallbackNode);478    }479480    let schedulerPriorityLevel;481    switch (lanesToEventPriority(nextLanes)) {482      // Scheduler does have an "ImmediatePriority", but now that we use483      // microtasks for sync work we no longer use that. Any sync work that484      // reaches this path is meant to be time sliced.485      case DiscreteEventPriority:486      case ContinuousEventPriority:487        schedulerPriorityLevel = UserBlockingSchedulerPriority;488        break;489      case DefaultEventPriority:490        schedulerPriorityLevel = NormalSchedulerPriority;491        break;492      case IdleEventPriority:493        schedulerPriorityLevel = IdleSchedulerPriority;494        break;495      default:496        schedulerPriorityLevel = NormalSchedulerPriority;497        break;498    }499500    const newCallbackNode = scheduleCallback(501      schedulerPriorityLevel,502      performWorkOnRootViaSchedulerTask.bind(null, root),503    );504505    root.callbackPriority = newCallbackPriority;506    root.callbackNode = newCallbackNode;507    return newCallbackPriority;508  }509}510511type RenderTaskFn = (didTimeout: boolean) => RenderTaskFn | null;512513function performWorkOnRootViaSchedulerTask(514  root: FiberRoot,515  didTimeout: boolean,516): RenderTaskFn | null {517  // This is the entry point for concurrent tasks scheduled via Scheduler (and518  // postTask, in the future).519520  if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {521    resetNestedUpdateFlag();522  }523524  if (enableProfilerTimer && enableComponentPerformanceTrack) {525    // Track the currently executing event if there is one so we can ignore this526    // event when logging events.527    trackSchedulerEvent();528  }529530  if (hasPendingCommitEffects()) {531    // We are currently in the middle of an async committing (such as a View Transition).532    // We could force these to flush eagerly but it's better to defer any work until533    // it finishes. This may not be the same root as we're waiting on.534    // TODO: This relies on the commit eventually calling ensureRootIsScheduled which535    // always calls processRootScheduleInMicrotask which in turn always loops through536    // all the roots to figure out. This is all a bit inefficient and if optimized537    // it'll need to consider rescheduling a task for any skipped roots.538    root.callbackNode = null;539    root.callbackPriority = NoLane;540    return null;541  }542543  // Flush any pending passive effects before deciding which lanes to work on,544  // in case they schedule additional work.545  const originalCallbackNode = root.callbackNode;546  const didFlushPassiveEffects = flushPendingEffectsDelayed();547  if (didFlushPassiveEffects) {548    // Something in the passive effect phase may have canceled the current task.549    // Check if the task node for this root was changed.550    if (root.callbackNode !== originalCallbackNode) {551      // The current task was canceled. Exit. We don't need to call552      // `ensureRootIsScheduled` because the check above implies either that553      // there's a new task, or that there's no remaining work on this root.554      return null;555    } else {556      // Current task was not canceled. Continue.557    }558  }559560  // Determine the next lanes to work on, using the fields stored on the root.561  // TODO: We already called getNextLanes when we scheduled the callback; we562  // should be able to avoid calling it again by stashing the result on the563  // root object. However, because we always schedule the callback during564  // a microtask (scheduleTaskForRootDuringMicrotask), it's possible that565  // an update was scheduled earlier during this same browser task (and566  // therefore before the microtasks have run). That's because Scheduler batches567  // together multiple callbacks into a single browser macrotask, without568  // yielding to microtasks in between. We should probably change this to align569  // with the postTask behavior (and literally use postTask when570  // it's available).571  const workInProgressRoot = getWorkInProgressRoot();572  const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();573  const rootHasPendingCommit =574    root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;575  const lanes = getNextLanes(576    root,577    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,578    rootHasPendingCommit,579  );580  if (lanes === NoLanes) {581    // No more work on this root.582    return null;583  }584585  // Enter the work loop.586  // TODO: We only check `didTimeout` defensively, to account for a Scheduler587  // bug we're still investigating. Once the bug in Scheduler is fixed,588  // we can remove this, since we track expiration ourselves.589  const forceSync = !disableSchedulerTimeoutInWorkLoop && didTimeout;590  performWorkOnRoot(root, lanes, forceSync);591592  // The work loop yielded, but there may or may not be work left at the current593  // priority. Need to determine whether we need to schedule a continuation.594  // Usually `scheduleTaskForRootDuringMicrotask` only runs inside a microtask;595  // however, since most of the logic for determining if we need a continuation596  // versus a new task is the same, we cheat a bit and call it here. This is597  // only safe to do because we know we're at the end of the browser task.598  // So although it's not an actual microtask, it might as well be.599  scheduleTaskForRootDuringMicrotask(root, now());600  if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {601    // The task node scheduled for this root is the same one that's602    // currently executed. Need to return a continuation.603    return performWorkOnRootViaSchedulerTask.bind(null, root);604  }605  return null;606}607608function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {609  // This is the entry point for synchronous tasks that don't go610  // through Scheduler.611  const didFlushPassiveEffects = flushPendingEffects();612  if (didFlushPassiveEffects) {613    // If passive effects were flushed, exit to the outer work loop in the root614    // scheduler, so we can recompute the priority.615    return null;616  }617  if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {618    syncNestedUpdateFlag();619  }620  const forceSync = true;621  performWorkOnRoot(root, lanes, forceSync);622}623624const fakeActCallbackNode = {};625626function scheduleCallback(627  priorityLevel: PriorityLevel,628  callback: RenderTaskFn,629) {630  if (__DEV__ && ReactSharedInternals.actQueue !== null) {631    // Special case: We're inside an `act` scope (a testing utility).632    // Instead of scheduling work in the host environment, add it to a633    // fake internal queue that's managed by the `act` implementation.634    ReactSharedInternals.actQueue.push(callback);635    return fakeActCallbackNode;636  } else {637    return Scheduler_scheduleCallback(priorityLevel, callback);638  }639}640641function cancelCallback(callbackNode: mixed) {642  if (__DEV__ && callbackNode === fakeActCallbackNode) {643    // Special `act` case: check if this is the fake callback node used by644    // the `act` implementation.645  } else if (callbackNode !== null) {646    Scheduler_cancelCallback(callbackNode);647  }648}649650function scheduleImmediateRootScheduleTask() {651  if (__DEV__ && ReactSharedInternals.actQueue !== null) {652    // Special case: Inside an `act` scope, we push microtasks to the fake `act`653    // callback queue. This is because we currently support calling `act`654    // without awaiting the result. The plan is to deprecate that, and require655    // that you always await the result so that the microtasks have a chance to656    // run. But it hasn't happened yet.657    ReactSharedInternals.actQueue.push(() => {658      processRootScheduleInMicrotask();659      return null;660    });661  }662663  // TODO: Can we land supportsMicrotasks? Which environments don't support it?664  // Alternatively, can we move this check to the host config?665  // $FlowFixMe[constant-condition]666  if (supportsMicrotasks) {667    scheduleMicrotask(() => {668      // In Safari, appending an iframe forces microtasks to run.669      // https://github.com/facebook/react/issues/22459670      // We don't support running callbacks in the middle of render671      // or commit so we need to check against that.672      const executionContext = getExecutionContext();673      if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {674        // Note that this would still prematurely flush the callbacks675        // if this happens outside render or commit phase (e.g. in an event).676677        // Intentionally using a macrotask instead of a microtask here. This is678        // wrong semantically but it prevents an infinite loop. The bug is679        // Safari's, not ours, so we just do our best to not crash even though680        // the behavior isn't completely correct.681        Scheduler_scheduleCallback(682          ImmediateSchedulerPriority,683          processRootScheduleInImmediateTask,684        );685        return;686      }687      processRootScheduleInMicrotask();688    });689  } else {690    // If microtasks are not supported, use Scheduler.691    Scheduler_scheduleCallback(692      ImmediateSchedulerPriority,693      processRootScheduleInImmediateTask,694    );695  }696}697698export function requestTransitionLane(699  // This argument isn't used, it's only here to encourage the caller to700  // check that it's inside a transition before calling this function.701  // TODO: Make this non-nullable. Requires a tweak to useOptimistic.702  transition: Transition | null,703): Lane {704  // The algorithm for assigning an update to a lane should be stable for all705  // updates at the same priority within the same event. To do this, the706  // inputs to the algorithm must be the same.707  //708  // The trick we use is to cache the first of each of these inputs within an709  // event. Then reset the cached values once we can be sure the event is710  // over. Our heuristic for that is whenever we enter a concurrent work loop.711  if (currentEventTransitionLane === NoLane) {712    // All transitions within the same event are assigned the same lane.713    const actionScopeLane = peekEntangledActionLane();714    currentEventTransitionLane =715      actionScopeLane !== NoLane716        ? // We're inside an async action scope. Reuse the same lane.717          actionScopeLane718        : // We may or may not be inside an async action scope. If we are, this719          // is the first update in that scope. Either way, we need to get a720          // fresh transition lane.721          claimNextTransitionUpdateLane();722  }723  return currentEventTransitionLane;724}725726export function didCurrentEventScheduleTransition(): boolean {727  return currentEventTransitionLane !== NoLane;728}729730export function markIndicatorHandled(root: FiberRoot): void {731  if (enableDefaultTransitionIndicator) {732    // The current transition event rendered a synchronous loading state.733    // Clear it from the indicator lanes. We don't need to show a separate734    // loading state for this lane.735    root.indicatorLanes &= ~currentEventTransitionLane;736    markIsomorphicIndicatorHandled();737  }738}

Code quality findings 46

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (root === lastScheduledRoot || root.next !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (lastScheduledRoot === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.tag === LegacyRoot
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (root !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (syncTransitionLanes !== NoLanes) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextLanes !== NoLanes) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.cancelPendingCommit !== null ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.timeoutHandle !== noTimeout;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root === workInProgressRoot
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (currentEventTransitionLane !== NoLane) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (root !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextLanes === NoLane) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (prev === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (next === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
syncTransitionLanes !== NoLanes ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (currentEventTransitionLane !== NoLane) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (root !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (root.indicatorLanes !== NoLanes && root.pendingIndicator === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
enableYieldingBeforePassive && root === rootWithPendingPassiveEffects
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
nextLanes === NoLanes ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.cancelPendingCommit !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (existingCallbackNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (existingCallbackNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
newCallbackPriority === existingCallbackPriority &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
ReactSharedInternals.actQueue !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
existingCallbackNode !== fakeActCallbackNode
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (lanesToEventPriority(nextLanes)) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (root.callbackNode !== originalCallbackNode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (lanes === NoLanes) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && callbackNode === fakeActCallbackNode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (callbackNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (currentEventTransitionLane === NoLane) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
actionScopeLane !== NoLane
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return currentEventTransitionLane !== NoLane;

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.