packages/react-reconciler/src/ReactFiberCompleteWork.js JAVASCRIPT 2,098 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,098.
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 {Fiber, FiberRoot} from './ReactInternalTypes';11import type {RootState} from './ReactFiberRoot';12import type {Lanes, Lane} from './ReactFiberLane';13import type {ReactScopeInstance, ReactContext} from 'shared/ReactTypes';14import type {15  Instance,16  Type,17  Props,18  Container,19  ChildSet,20  Resource,21} from './ReactFiberConfig';22import type {ActivityState} from './ReactFiberActivityComponent';23import type {24  SuspenseState,25  SuspenseListRenderState,26  RetryQueue,27} from './ReactFiberSuspenseComponent';28import type {29  OffscreenState,30  OffscreenQueue,31} from './ReactFiberOffscreenComponent';32import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';33import type {Cache} from './ReactFiberCacheComponent';34import {35  enableLegacyHidden,36  enableSuspenseCallback,37  enableScopeAPI,38  enableProfilerTimer,39  enableTransitionTracing,40  passChildrenWhenCloningPersistedNodes,41  disableLegacyMode,42  enableViewTransition,43  enableSuspenseyImages,44} from 'shared/ReactFeatureFlags';4546import {now} from './Scheduler';4748import {49  FunctionComponent,50  ClassComponent,51  HostRoot,52  HostComponent,53  HostHoistable,54  HostSingleton,55  HostText,56  HostPortal,57  ContextProvider,58  ContextConsumer,59  ForwardRef,60  Fragment,61  Mode,62  Profiler,63  SuspenseComponent,64  SuspenseListComponent,65  MemoComponent,66  SimpleMemoComponent,67  LazyComponent,68  IncompleteClassComponent,69  IncompleteFunctionComponent,70  ScopeComponent,71  OffscreenComponent,72  LegacyHiddenComponent,73  CacheComponent,74  TracingMarkerComponent,75  Throw,76  ViewTransitionComponent,77  ActivityComponent,78} from './ReactWorkTags';79import {80  NoMode,81  ConcurrentMode,82  ProfileMode,83  SuspenseyImagesMode,84} from './ReactTypeOfMode';85import {86  Placement,87  Update,88  Visibility,89  NoFlags,90  DidCapture,91  Snapshot,92  ChildDeletion,93  StaticMask,94  Passive,95  ForceClientRender,96  MaySuspendCommit,97  ScheduleRetry,98  ShouldSuspendCommit,99  Cloned,100  ViewTransitionStatic,101  Hydrate,102  PortalStatic,103} from './ReactFiberFlags';104105import {106  createInstance,107  createTextInstance,108  resolveSingletonInstance,109  appendInitialChild,110  finalizeInitialChildren,111  finalizeHydratedChildren,112  supportsMutation,113  supportsPersistence,114  supportsResources,115  supportsSingletons,116  cloneInstance,117  cloneHiddenInstance,118  cloneHiddenTextInstance,119  createContainerChildSet,120  appendChildToContainerChildSet,121  finalizeContainerChildren,122  preparePortalMount,123  prepareScopeUpdate,124  maySuspendCommit,125  maySuspendCommitOnUpdate,126  maySuspendCommitInSyncRender,127  mayResourceSuspendCommit,128  preloadInstance,129  preloadResource,130} from './ReactFiberConfig';131import {132  getRootHostContainer,133  popHostContext,134  getHostContext,135  popHostContainer,136} from './ReactFiberHostContext';137import {138  suspenseStackCursor,139  popSuspenseListContext,140  popSuspenseHandler,141  pushSuspenseListContext,142  pushSuspenseListCatch,143  setShallowSuspenseListContext,144  ForceSuspenseFallback,145  setDefaultShallowSuspenseListContext,146} from './ReactFiberSuspenseContext';147import {popHiddenContext} from './ReactFiberHiddenContext';148import {findFirstSuspended} from './ReactFiberSuspenseComponent';149import {150  isContextProvider as isLegacyContextProvider,151  popContext as popLegacyContext,152  popTopLevelContextObject as popTopLevelLegacyContextObject,153} from './ReactFiberLegacyContext';154import {popProvider} from './ReactFiberNewContext';155import {156  prepareToHydrateHostInstance,157  prepareToHydrateHostTextInstance,158  prepareToHydrateHostActivityInstance,159  prepareToHydrateHostSuspenseInstance,160  popHydrationState,161  resetHydrationState,162  getIsHydrating,163  upgradeHydrationErrorsToRecoverable,164  emitPendingHydrationWarnings,165} from './ReactFiberHydrationContext';166import {167  renderHasNotSuspendedYet,168  getRenderTargetTime,169  getWorkInProgressTransitions,170  shouldRemainOnPreviousScreen,171  markSpawnedRetryLane,172} from './ReactFiberWorkLoop';173import {174  OffscreenLane,175  SomeRetryLane,176  NoLanes,177  includesSomeLane,178  mergeLanes,179  claimNextRetryLane,180  includesOnlySuspenseyCommitEligibleLanes,181} from './ReactFiberLane';182import {resetChildFibers} from './ReactChildFiber';183import {createScopeInstance} from './ReactFiberScope';184import {transferActualDuration} from './ReactProfilerTimer';185import {popCacheProvider} from './ReactFiberCacheComponent';186import {popTreeContext, pushTreeFork} from './ReactFiberTreeContext';187import {popRootTransition, popTransition} from './ReactFiberTransition';188import {189  popMarkerInstance,190  popRootMarkerInstance,191} from './ReactFiberTracingMarkerComponent';192import {suspendCommit} from './ReactFiberThenable';193import type {Flags} from './ReactFiberFlags';194195/**196 * Tag the fiber with an update effect. This turns a Placement into197 * a PlacementAndUpdate.198 */199function markUpdate(workInProgress: Fiber) {200  workInProgress.flags |= Update;201}202203/**204 * Tag the fiber with Cloned in persistent mode to signal that205 * it received an update that requires a clone of the tree above.206 */207function markCloned(workInProgress: Fiber) {208  // $FlowFixMe[constant-condition]209  if (supportsPersistence) {210    workInProgress.flags |= Cloned;211  }212}213214/**215 * In persistent mode, return whether this update needs to clone the subtree.216 */217function doesRequireClone(current: null | Fiber, completedWork: Fiber) {218  const didBailout = current !== null && current.child === completedWork.child;219  if (didBailout) {220    return false;221  }222223  if ((completedWork.flags & ChildDeletion) !== NoFlags) {224    return true;225  }226227  // TODO: If we move the `doesRequireClone` call after `bubbleProperties`228  // then we only have to check the `completedWork.subtreeFlags`.229  let child = completedWork.child;230  while (child !== null) {231    const checkedFlags = Cloned | Visibility | Placement | ChildDeletion;232    if (233      (child.flags & checkedFlags) !== NoFlags ||234      (child.subtreeFlags & checkedFlags) !== NoFlags235    ) {236      return true;237    }238    child = child.sibling;239  }240  return false;241}242243function appendAllChildren(244  parent: Instance,245  workInProgress: Fiber,246  needsVisibilityToggle: boolean,247  isHidden: boolean,248) {249  // $FlowFixMe[constant-condition]250  if (supportsMutation) {251    // We only have the top Fiber that was created but we need recurse down its252    // children to find all the terminal nodes.253    let node = workInProgress.child;254    while (node !== null) {255      if (node.tag === HostComponent || node.tag === HostText) {256        appendInitialChild(parent, node.stateNode);257      } else if (258        node.tag === HostPortal ||259        // $FlowFixMe[constant-condition]260        (supportsSingletons ? node.tag === HostSingleton : false)261      ) {262        // If we have a portal child, then we don't want to traverse263        // down its children. Instead, we'll get insertions from each child in264        // the portal directly.265        // If we have a HostSingleton it will be placed independently266      } else if (node.child !== null) {267        node.child.return = node;268        node = node.child;269        continue;270      }271      if (node === workInProgress) {272        return;273      }274      // $FlowFixMe[incompatible-use] found when upgrading Flow275      while (node.sibling === null) {276        // $FlowFixMe[incompatible-use] found when upgrading Flow277        if (node.return === null || node.return === workInProgress) {278          return;279        }280        node = node.return;281      }282      // $FlowFixMe[incompatible-use] found when upgrading Flow283      node.sibling.return = node.return;284      node = node.sibling;285    }286    // $FlowFixMe[constant-condition]287  } else if (supportsPersistence) {288    // We only have the top Fiber that was created but we need recurse down its289    // children to find all the terminal nodes.290    let node = workInProgress.child;291    while (node !== null) {292      if (node.tag === HostComponent) {293        let instance = node.stateNode;294        if (needsVisibilityToggle && isHidden) {295          // This child is inside a timed out tree. Hide it.296          const props = node.memoizedProps;297          const type = node.type;298          instance = cloneHiddenInstance(instance, type, props);299        }300        appendInitialChild(parent, instance);301      } else if (node.tag === HostText) {302        let instance = node.stateNode;303        if (needsVisibilityToggle && isHidden) {304          // This child is inside a timed out tree. Hide it.305          const text = node.memoizedProps;306          instance = cloneHiddenTextInstance(instance, text);307        }308        appendInitialChild(parent, instance);309      } else if (node.tag === HostPortal) {310        // If we have a portal child, then we don't want to traverse311        // down its children. Instead, we'll get insertions from each child in312        // the portal directly.313      } else if (314        node.tag === OffscreenComponent &&315        node.memoizedState !== null316      ) {317        // The children in this boundary are hidden. Toggle their visibility318        // before appending.319        const child = node.child;320        if (child !== null) {321          child.return = node;322        }323        appendAllChildren(324          parent,325          node,326          /* needsVisibilityToggle */ true,327          /* isHidden */ true,328        );329      } else if (node.child !== null) {330        node.child.return = node;331        node = node.child;332        continue;333      }334      if (node === workInProgress) {335        return;336      }337      // $FlowFixMe[incompatible-use] found when upgrading Flow338      while (node.sibling === null) {339        // $FlowFixMe[incompatible-use] found when upgrading Flow340        if (node.return === null || node.return === workInProgress) {341          return;342        }343        node = node.return;344      }345      // $FlowFixMe[incompatible-use] found when upgrading Flow346      node.sibling.return = node.return;347      node = node.sibling;348    }349  }350}351352// An unfortunate fork of appendAllChildren because we have two different parent types.353function appendAllChildrenToContainer(354  containerChildSet: ChildSet,355  workInProgress: Fiber,356  needsVisibilityToggle: boolean,357  isHidden: boolean,358): boolean {359  // Host components that have their visibility toggled by an OffscreenComponent360  // do not support passChildrenWhenCloningPersistedNodes. To inform the callee361  // about their presence, we track and return if they were added to the362  // child set.363  let hasOffscreenComponentChild = false;364  // $FlowFixMe[constant-condition]365  if (supportsPersistence) {366    // We only have the top Fiber that was created but we need recurse down its367    // children to find all the terminal nodes.368    let node = workInProgress.child;369    while (node !== null) {370      if (node.tag === HostComponent) {371        let instance = node.stateNode;372        if (needsVisibilityToggle && isHidden) {373          // This child is inside a timed out tree. Hide it.374          const props = node.memoizedProps;375          const type = node.type;376          instance = cloneHiddenInstance(instance, type, props);377        }378        appendChildToContainerChildSet(containerChildSet, instance);379      } else if (node.tag === HostText) {380        let instance = node.stateNode;381        if (needsVisibilityToggle && isHidden) {382          // This child is inside a timed out tree. Hide it.383          const text = node.memoizedProps;384          instance = cloneHiddenTextInstance(instance, text);385        }386        appendChildToContainerChildSet(containerChildSet, instance);387      } else if (node.tag === HostPortal) {388        // If we have a portal child, then we don't want to traverse389        // down its children. Instead, we'll get insertions from each child in390        // the portal directly.391      } else if (392        node.tag === OffscreenComponent &&393        node.memoizedState !== null394      ) {395        // The children in this boundary are hidden. Toggle their visibility396        // before appending.397        const child = node.child;398        if (child !== null) {399          child.return = node;400        }401        appendAllChildrenToContainer(402          containerChildSet,403          node,404          /* needsVisibilityToggle */ true,405          /* isHidden */ true,406        );407408        hasOffscreenComponentChild = true;409      } else if (node.child !== null) {410        node.child.return = node;411        node = node.child;412        continue;413      }414      node = node as Fiber;415      if (node === workInProgress) {416        return hasOffscreenComponentChild;417      }418      // $FlowFixMe[incompatible-use] found when upgrading Flow419      while (node.sibling === null) {420        // $FlowFixMe[incompatible-use] found when upgrading Flow421        if (node.return === null || node.return === workInProgress) {422          return hasOffscreenComponentChild;423        }424        node = node.return;425      }426      // $FlowFixMe[incompatible-use] found when upgrading Flow427      node.sibling.return = node.return;428      node = node.sibling;429    }430  }431432  return hasOffscreenComponentChild;433}434435function updateHostContainer(current: null | Fiber, workInProgress: Fiber) {436  // $FlowFixMe[constant-condition]437  if (supportsPersistence) {438    if (doesRequireClone(current, workInProgress)) {439      const portalOrRoot: {440        containerInfo: Container,441        pendingChildren: ChildSet,442        ...443      } = workInProgress.stateNode;444      const container = portalOrRoot.containerInfo;445      const newChildSet = createContainerChildSet();446      // If children might have changed, we have to add them all to the set.447      appendAllChildrenToContainer(448        newChildSet,449        workInProgress,450        /* needsVisibilityToggle */ false,451        /* isHidden */ false,452      );453      portalOrRoot.pendingChildren = newChildSet;454      // Schedule an update on the container to swap out the container.455      markUpdate(workInProgress);456      finalizeContainerChildren(container, newChildSet);457    }458  }459}460461function updateHostComponent(462  current: Fiber,463  workInProgress: Fiber,464  type: Type,465  newProps: Props,466  renderLanes: Lanes,467) {468  // $FlowFixMe[constant-condition]469  if (supportsMutation) {470    // If we have an alternate, that means this is an update and we need to471    // schedule a side-effect to do the updates.472    const oldProps = current.memoizedProps;473    if (oldProps === newProps) {474      // In mutation mode, this is sufficient for a bailout because475      // we won't touch this node even if children changed.476      return;477    }478479    markUpdate(workInProgress);480    // $FlowFixMe[constant-condition]481  } else if (supportsPersistence) {482    const currentInstance = current.stateNode;483    const oldProps = current.memoizedProps;484    // If there are no effects associated with this node, then none of our children had any updates.485    // This guarantees that we can reuse all of them.486    const requiresClone = doesRequireClone(current, workInProgress);487    if (!requiresClone && oldProps === newProps) {488      // No changes, just reuse the existing instance.489      // Note that this might release a previous clone.490      workInProgress.stateNode = currentInstance;491      return;492    }493    const currentHostContext = getHostContext();494495    let newChildSet = null;496    let hasOffscreenComponentChild = false;497    if (requiresClone && passChildrenWhenCloningPersistedNodes) {498      markCloned(workInProgress);499      newChildSet = createContainerChildSet();500      // If children might have changed, we have to add them all to the set.501      hasOffscreenComponentChild = appendAllChildrenToContainer(502        newChildSet,503        workInProgress,504        /* needsVisibilityToggle */ false,505        /* isHidden */ false,506      );507    }508509    const newInstance = cloneInstance(510      currentInstance,511      type,512      oldProps,513      newProps,514      !requiresClone,515      !hasOffscreenComponentChild ? newChildSet : undefined,516    );517    if (newInstance === currentInstance) {518      // No changes, just reuse the existing instance.519      // Note that this might release a previous clone.520      workInProgress.stateNode = currentInstance;521      return;522    } else {523      markCloned(workInProgress);524    }525526    // Certain renderers require commit-time effects for initial mount.527    // (eg DOM renderer supports auto-focus for certain elements).528    // Make sure such renderers get scheduled for later work.529    if (530      finalizeInitialChildren(newInstance, type, newProps, currentHostContext)531    ) {532      markUpdate(workInProgress);533    }534    workInProgress.stateNode = newInstance;535    if (536      requiresClone &&537      (!passChildrenWhenCloningPersistedNodes || hasOffscreenComponentChild)538    ) {539      // If children have changed, we have to add them all to the set.540      appendAllChildren(541        newInstance,542        workInProgress,543        /* needsVisibilityToggle */ false,544        /* isHidden */ false,545      );546    }547  }548}549550// This function must be called at the very end of the complete phase, because551// it might throw to suspend, and if the resource immediately loads, the work552// loop will resume rendering as if the work-in-progress completed. So it must553// fully complete.554// TODO: This should ideally move to begin phase, but currently the instance is555// not created until the complete phase. For our existing use cases, host nodes556// that suspend don't have children, so it doesn't matter. But that might not557// always be true in the future.558function preloadInstanceAndSuspendIfNeeded(559  workInProgress: Fiber,560  type: Type,561  oldProps: null | Props,562  newProps: Props,563  renderLanes: Lanes,564) {565  const maySuspend =566    (enableSuspenseyImages ||567      (workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&568    (oldProps === null569      ? maySuspendCommit(type, newProps)570      : maySuspendCommitOnUpdate(type, oldProps, newProps));571572  if (!maySuspend) {573    // If this flag was set previously, we can remove it. The flag574    // represents whether this particular set of props might ever need to575    // suspend. The safest thing to do is for maySuspendCommit to always576    // return true, but if the renderer is reasonably confident that the577    // underlying resource won't be evicted, it can return false as a578    // performance optimization.579    workInProgress.flags &= ~MaySuspendCommit;580    return;581  }582583  // Mark this fiber with a flag. This gets set on all host instances584  // that might possibly suspend, even if they don't need to suspend585  // currently. We use this when revealing a prerendered tree, because586  // even though the tree has "mounted", its resources might not have587  // loaded yet.588  workInProgress.flags |= MaySuspendCommit;589590  if (591    includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||592    maySuspendCommitInSyncRender(type, newProps)593  ) {594    // preload the instance if necessary. Even if this is an urgent render there595    // could be benefits to preloading early.596    // @TODO we should probably do the preload in begin work597    const isReady = preloadInstance(workInProgress.stateNode, type, newProps);598    if (!isReady) {599      if (shouldRemainOnPreviousScreen()) {600        workInProgress.flags |= ShouldSuspendCommit;601      } else {602        suspendCommit();603      }604    } else {605      // Even if we're ready we suspend the commit and check again in the pre-commit606      // phase if we need to suspend anyway. Such as if it's delayed on decoding or607      // if it was dropped from the cache while rendering due to pressure.608      workInProgress.flags |= ShouldSuspendCommit;609    }610  }611}612613function preloadResourceAndSuspendIfNeeded(614  workInProgress: Fiber,615  resource: Resource,616  type: Type,617  props: Props,618  renderLanes: Lanes,619) {620  // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.621  if (!mayResourceSuspendCommit(resource)) {622    workInProgress.flags &= ~MaySuspendCommit;623    return;624  }625626  workInProgress.flags |= MaySuspendCommit;627628  const isReady = preloadResource(resource);629  if (!isReady) {630    if (shouldRemainOnPreviousScreen()) {631      workInProgress.flags |= ShouldSuspendCommit;632    } else {633      suspendCommit();634    }635  }636}637638function scheduleRetryEffect(639  workInProgress: Fiber,640  retryQueue: RetryQueue | null,641) {642  const wakeables = retryQueue;643  if (wakeables !== null) {644    // Schedule an effect to attach a retry listener to the promise.645    // TODO: Move to passive phase646    workInProgress.flags |= Update;647  }648649  // Check if we need to schedule an immediate retry. This should happen650  // whenever we unwind a suspended tree without fully rendering its siblings;651  // we need to begin the retry so we can start prerendering them.652  //653  // We also use this mechanism for Suspensey Resources (e.g. stylesheets),654  // because those don't actually block the render phase, only the commit phase.655  // So we can start rendering even before the resources are ready.656  if (workInProgress.flags & ScheduleRetry) {657    const retryLane =658      // TODO: This check should probably be moved into claimNextRetryLane659      // I also suspect that we need some further consolidation of offscreen660      // and retry lanes.661      workInProgress.tag !== OffscreenComponent662        ? claimNextRetryLane()663        : OffscreenLane;664    workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);665666    // Track the lanes that have been scheduled for an immediate retry so that667    // we can mark them as suspended upon committing the root.668    markSpawnedRetryLane(retryLane);669  }670}671672function updateHostText(673  current: Fiber,674  workInProgress: Fiber,675  oldText: string,676  newText: string,677) {678  // $FlowFixMe[constant-condition]679  if (supportsMutation) {680    // If the text differs, mark it as an update. All the work in done in commitWork.681    if (oldText !== newText) {682      markUpdate(workInProgress);683    }684    // $FlowFixMe[constant-condition]685  } else if (supportsPersistence) {686    if (oldText !== newText) {687      // If the text content differs, we'll create a new text instance for it.688      const rootContainerInstance = getRootHostContainer();689      const currentHostContext = getHostContext();690      markCloned(workInProgress);691      workInProgress.stateNode = createTextInstance(692        newText,693        rootContainerInstance,694        currentHostContext,695        workInProgress,696      );697    } else {698      workInProgress.stateNode = current.stateNode;699    }700  }701}702703function cutOffTailIfNeeded(704  renderState: SuspenseListRenderState,705  hasRenderedATailFallback: boolean,706) {707  if (getIsHydrating()) {708    // If we're hydrating, we should consume as many items as we can709    // so we don't leave any behind.710    return;711  }712  switch (renderState.tailMode) {713    case 'visible': {714      // Everything should remain as it was.715      break;716    }717    case 'collapsed': {718      // Any insertions at the end of the tail list after this point719      // should be invisible. If there are already mounted boundaries720      // anything before them are not considered for collapsing.721      // Therefore we need to go through the whole tail to find if722      // there are any.723      let tailNode = renderState.tail;724      let lastTailNode = null;725      while (tailNode !== null) {726        if (tailNode.alternate !== null) {727          lastTailNode = tailNode;728        }729        tailNode = tailNode.sibling;730      }731      // Next we're simply going to delete all insertions after the732      // last rendered item.733      if (lastTailNode === null) {734        // All remaining items in the tail are insertions.735        if (!hasRenderedATailFallback && renderState.tail !== null) {736          // We suspended during the head. We want to show at least one737          // row at the tail. So we'll keep on and cut off the rest.738          renderState.tail.sibling = null;739        } else {740          renderState.tail = null;741        }742      } else {743        // Detach the insertion after the last node that was already744        // inserted.745        lastTailNode.sibling = null;746      }747      break;748    }749    // Hidden is now the default.750    case 'hidden':751    default: {752      // Any insertions at the end of the tail list after this point753      // should be invisible. If there are already mounted boundaries754      // anything before them are not considered for collapsing.755      // Therefore we need to go through the whole tail to find if756      // there are any.757      let tailNode = renderState.tail;758      let lastTailNode = null;759      while (tailNode !== null) {760        if (tailNode.alternate !== null) {761          lastTailNode = tailNode;762        }763        tailNode = tailNode.sibling;764      }765      // Next we're simply going to delete all insertions after the766      // last rendered item.767      if (lastTailNode === null) {768        // All remaining items in the tail are insertions.769        renderState.tail = null;770      } else {771        // Detach the insertion after the last node that was already772        // inserted.773        lastTailNode.sibling = null;774      }775      break;776    }777  }778}779780function isOnlyNewMounts(tail: Fiber): boolean {781  let fiber: null | Fiber = tail;782  while (fiber !== null) {783    if (fiber.alternate !== null) {784      return false;785    }786    fiber = fiber.sibling;787  }788  return true;789}790791function bubbleProperties(completedWork: Fiber) {792  const didBailout =793    completedWork.alternate !== null &&794    completedWork.alternate.child === completedWork.child;795796  let newChildLanes: Lanes = NoLanes;797  let subtreeFlags: Flags = NoFlags;798799  if (!didBailout) {800    // Bubble up the earliest expiration time.801    if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {802      // In profiling mode, resetChildExpirationTime is also used to reset803      // profiler durations.804      let actualDuration = completedWork.actualDuration;805      let treeBaseDuration = completedWork.selfBaseDuration as any as number;806807      let child = completedWork.child;808      while (child !== null) {809        newChildLanes = mergeLanes(810          newChildLanes,811          mergeLanes(child.lanes, child.childLanes),812        );813814        subtreeFlags |= child.subtreeFlags;815        subtreeFlags |= child.flags;816817        // When a fiber is cloned, its actualDuration is reset to 0. This value will818        // only be updated if work is done on the fiber (i.e. it doesn't bailout).819        // When work is done, it should bubble to the parent's actualDuration. If820        // the fiber has not been cloned though, (meaning no work was done), then821        // this value will reflect the amount of time spent working on a previous822        // render. In that case it should not bubble. We determine whether it was823        // cloned by comparing the child pointer.824        // $FlowFixMe[unsafe-addition] addition with possible null/undefined value825        actualDuration += child.actualDuration;826827        // $FlowFixMe[unsafe-addition] addition with possible null/undefined value828        treeBaseDuration += child.treeBaseDuration;829        child = child.sibling;830      }831832      completedWork.actualDuration = actualDuration;833      completedWork.treeBaseDuration = treeBaseDuration;834    } else {835      let child = completedWork.child;836      while (child !== null) {837        newChildLanes = mergeLanes(838          newChildLanes,839          mergeLanes(child.lanes, child.childLanes),840        );841842        subtreeFlags |= child.subtreeFlags;843        subtreeFlags |= child.flags;844845        // Update the return pointer so the tree is consistent. This is a code846        // smell because it assumes the commit phase is never concurrent with847        // the render phase. Will address during refactor to alternate model.848        child.return = completedWork;849850        child = child.sibling;851      }852    }853854    completedWork.subtreeFlags |= subtreeFlags;855  } else {856    // Bubble up the earliest expiration time.857    if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {858      // In profiling mode, resetChildExpirationTime is also used to reset859      // profiler durations.860      let treeBaseDuration = completedWork.selfBaseDuration as any as number;861862      let child = completedWork.child;863      while (child !== null) {864        newChildLanes = mergeLanes(865          newChildLanes,866          mergeLanes(child.lanes, child.childLanes),867        );868869        // "Static" flags share the lifetime of the fiber/hook they belong to,870        // so we should bubble those up even during a bailout. All the other871        // flags have a lifetime only of a single render + commit, so we should872        // ignore them.873        subtreeFlags |= child.subtreeFlags & StaticMask;874        subtreeFlags |= child.flags & StaticMask;875876        // $FlowFixMe[unsafe-addition] addition with possible null/undefined value877        treeBaseDuration += child.treeBaseDuration;878        child = child.sibling;879      }880881      completedWork.treeBaseDuration = treeBaseDuration;882    } else {883      let child = completedWork.child;884      while (child !== null) {885        newChildLanes = mergeLanes(886          newChildLanes,887          mergeLanes(child.lanes, child.childLanes),888        );889890        // "Static" flags share the lifetime of the fiber/hook they belong to,891        // so we should bubble those up even during a bailout. All the other892        // flags have a lifetime only of a single render + commit, so we should893        // ignore them.894        subtreeFlags |= child.subtreeFlags & StaticMask;895        subtreeFlags |= child.flags & StaticMask;896897        // Update the return pointer so the tree is consistent. This is a code898        // smell because it assumes the commit phase is never concurrent with899        // the render phase. Will address during refactor to alternate model.900        child.return = completedWork;901902        child = child.sibling;903      }904    }905906    completedWork.subtreeFlags |= subtreeFlags;907  }908909  completedWork.childLanes = newChildLanes;910911  return didBailout;912}913914function completeDehydratedActivityBoundary(915  current: Fiber | null,916  workInProgress: Fiber,917  nextState: ActivityState | null,918): boolean {919  const wasHydrated = popHydrationState(workInProgress);920921  if (nextState !== null) {922    // We might be inside a hydration state the first time we're picking up this923    // Activity boundary, and also after we've reentered it for further hydration.924    if (current === null) {925      if (!wasHydrated) {926        throw new Error(927          'A dehydrated suspense component was completed without a hydrated node. ' +928            'This is probably a bug in React.',929        );930      }931      prepareToHydrateHostActivityInstance(workInProgress);932      bubbleProperties(workInProgress);933      if (enableProfilerTimer) {934        if ((workInProgress.mode & ProfileMode) !== NoMode) {935          // $FlowFixMe[invalid-compare]936          const isTimedOutSuspense = nextState !== null;937          if (isTimedOutSuspense) {938            // Don't count time spent in a timed out Suspense subtree as part of the base duration.939            const primaryChildFragment = workInProgress.child;940            if (primaryChildFragment !== null) {941              // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator942              workInProgress.treeBaseDuration -=943                primaryChildFragment.treeBaseDuration as any as number;944            }945          }946        }947      }948      return false;949    } else {950      emitPendingHydrationWarnings();951      // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration952      // state since we're now exiting out of it. popHydrationState doesn't do that for us.953      resetHydrationState();954      if ((workInProgress.flags & DidCapture) === NoFlags) {955        // This boundary did not suspend so it's now hydrated and unsuspended.956        nextState = workInProgress.memoizedState = null;957      }958      // If nothing suspended, we need to schedule an effect to mark this boundary959      // as having hydrated so events know that they're free to be invoked.960      // It's also a signal to replay events and the suspense callback.961      // If something suspended, schedule an effect to attach retry listeners.962      // So we might as well always mark this.963      workInProgress.flags |= Update;964      bubbleProperties(workInProgress);965      if (enableProfilerTimer) {966        if ((workInProgress.mode & ProfileMode) !== NoMode) {967          const isTimedOutSuspense = nextState !== null;968          if (isTimedOutSuspense) {969            // Don't count time spent in a timed out Suspense subtree as part of the base duration.970            const primaryChildFragment = workInProgress.child;971            if (primaryChildFragment !== null) {972              // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator973              workInProgress.treeBaseDuration -=974                primaryChildFragment.treeBaseDuration as any as number;975            }976          }977        }978      }979      return false;980    }981  } else {982    // Successfully completed this tree. If this was a forced client render,983    // there may have been recoverable errors during first hydration984    // attempt. If so, add them to a queue so we can log them in the985    // commit phase. We also add them to prev state so we can get to them986    // from the Suspense Boundary.987    const hydrationErrors = upgradeHydrationErrorsToRecoverable();988    if (current !== null && current.memoizedState !== null) {989      const prevState: ActivityState = current.memoizedState;990      prevState.hydrationErrors = hydrationErrors;991    }992    // Fall through to normal Offscreen path993    return true;994  }995}996997function completeDehydratedSuspenseBoundary(998  current: Fiber | null,999  workInProgress: Fiber,1000  nextState: SuspenseState | null,1001): boolean {1002  const wasHydrated = popHydrationState(workInProgress);10031004  if (nextState !== null && nextState.dehydrated !== null) {1005    // We might be inside a hydration state the first time we're picking up this1006    // Suspense boundary, and also after we've reentered it for further hydration.1007    if (current === null) {1008      if (!wasHydrated) {1009        throw new Error(1010          'A dehydrated suspense component was completed without a hydrated node. ' +1011            'This is probably a bug in React.',1012        );1013      }1014      prepareToHydrateHostSuspenseInstance(workInProgress);1015      bubbleProperties(workInProgress);1016      if (enableProfilerTimer) {1017        if ((workInProgress.mode & ProfileMode) !== NoMode) {1018          // $FlowFixMe[invalid-compare]1019          const isTimedOutSuspense = nextState !== null;1020          if (isTimedOutSuspense) {1021            // Don't count time spent in a timed out Suspense subtree as part of the base duration.1022            const primaryChildFragment = workInProgress.child;1023            if (primaryChildFragment !== null) {1024              // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1025              workInProgress.treeBaseDuration -=1026                primaryChildFragment.treeBaseDuration as any as number;1027            }1028          }1029        }1030      }1031      return false;1032    } else {1033      emitPendingHydrationWarnings();1034      // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration1035      // state since we're now exiting out of it. popHydrationState doesn't do that for us.1036      resetHydrationState();1037      if ((workInProgress.flags & DidCapture) === NoFlags) {1038        // This boundary did not suspend so it's now hydrated and unsuspended.1039        nextState = workInProgress.memoizedState = null;1040      }1041      // If nothing suspended, we need to schedule an effect to mark this boundary1042      // as having hydrated so events know that they're free to be invoked.1043      // It's also a signal to replay events and the suspense callback.1044      // If something suspended, schedule an effect to attach retry listeners.1045      // So we might as well always mark this.1046      workInProgress.flags |= Update;1047      bubbleProperties(workInProgress);1048      if (enableProfilerTimer) {1049        if ((workInProgress.mode & ProfileMode) !== NoMode) {1050          const isTimedOutSuspense = nextState !== null;1051          if (isTimedOutSuspense) {1052            // Don't count time spent in a timed out Suspense subtree as part of the base duration.1053            const primaryChildFragment = workInProgress.child;1054            if (primaryChildFragment !== null) {1055              // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1056              workInProgress.treeBaseDuration -=1057                primaryChildFragment.treeBaseDuration as any as number;1058            }1059          }1060        }1061      }1062      return false;1063    }1064  } else {1065    // Successfully completed this tree. If this was a forced client render,1066    // there may have been recoverable errors during first hydration1067    // attempt. If so, add them to a queue so we can log them in the1068    // commit phase. We also add them to prev state so we can get to them1069    // from the Suspense Boundary.1070    const hydrationErrors = upgradeHydrationErrorsToRecoverable();1071    if (current !== null && current.memoizedState !== null) {1072      const prevState: SuspenseState = current.memoizedState;1073      prevState.hydrationErrors = hydrationErrors;1074    }1075    // Fall through to normal Suspense path1076    return true;1077  }1078}10791080function completeWork(1081  current: Fiber | null,1082  workInProgress: Fiber,1083  renderLanes: Lanes,1084): Fiber | null {1085  const newProps = workInProgress.pendingProps;1086  // Note: This intentionally doesn't check if we're hydrating because comparing1087  // to the current tree provider fiber is just as fast and less error-prone.1088  // Ideally we would have a special version of the work loop only1089  // for hydration.1090  popTreeContext(workInProgress);1091  switch (workInProgress.tag) {1092    case IncompleteFunctionComponent: {1093      if (disableLegacyMode) {1094        break;1095      }1096      // Fallthrough1097    }1098    case LazyComponent:1099    case SimpleMemoComponent:1100    case FunctionComponent:1101    case ForwardRef:1102    case Fragment:1103    case Mode:1104    case Profiler:1105    case ContextConsumer:1106    case MemoComponent:1107      bubbleProperties(workInProgress);1108      return null;1109    case ClassComponent: {1110      const Component = workInProgress.type;1111      if (isLegacyContextProvider(Component)) {1112        popLegacyContext(workInProgress);1113      }1114      bubbleProperties(workInProgress);1115      return null;1116    }1117    case HostRoot: {1118      const fiberRoot = workInProgress.stateNode as FiberRoot;11191120      if (enableTransitionTracing) {1121        const transitions = getWorkInProgressTransitions();1122        // We set the Passive flag here because if there are new transitions,1123        // we will need to schedule callbacks and process the transitions,1124        // which we do in the passive phase1125        if (transitions !== null) {1126          workInProgress.flags |= Passive;1127        }1128      }11291130      let previousCache: Cache | null = null;1131      if (current !== null) {1132        previousCache = current.memoizedState.cache;1133      }1134      const cache: Cache = workInProgress.memoizedState.cache;1135      if (cache !== previousCache) {1136        // Run passive effects to retain/release the cache.1137        workInProgress.flags |= Passive;1138      }1139      popCacheProvider(workInProgress, cache);11401141      if (enableTransitionTracing) {1142        popRootMarkerInstance(workInProgress);1143      }11441145      popRootTransition(workInProgress, fiberRoot, renderLanes);1146      popHostContainer(workInProgress);1147      popTopLevelLegacyContextObject(workInProgress);1148      if (fiberRoot.pendingContext) {1149        fiberRoot.context = fiberRoot.pendingContext;1150        fiberRoot.pendingContext = null;1151      }1152      if (current === null || current.child === null) {1153        // If we hydrated, pop so that we can delete any remaining children1154        // that weren't hydrated.1155        const wasHydrated = popHydrationState(workInProgress);1156        if (wasHydrated) {1157          emitPendingHydrationWarnings();1158          // If we hydrated, then we'll need to schedule an update for1159          // the commit side-effects on the root.1160          markUpdate(workInProgress);1161        } else {1162          if (current !== null) {1163            const prevState: RootState = current.memoizedState;1164            if (1165              // Check if this is a client root1166              !prevState.isDehydrated ||1167              // Check if we reverted to client rendering (e.g. due to an error)1168              (workInProgress.flags & ForceClientRender) !== NoFlags1169            ) {1170              // Schedule an effect to clear this container at the start of the1171              // next commit. This handles the case of React rendering into a1172              // container with previous children. It's also safe to do for1173              // updates too, because current.child would only be null if the1174              // previous render was null (so the container would already1175              // be empty).1176              workInProgress.flags |= Snapshot;11771178              // If this was a forced client render, there may have been1179              // recoverable errors during first hydration attempt. If so, add1180              // them to a queue so we can log them in the commit phase.1181              upgradeHydrationErrorsToRecoverable();1182            }1183          }1184        }1185      }1186      updateHostContainer(current, workInProgress);1187      bubbleProperties(workInProgress);1188      if (enableTransitionTracing) {1189        if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {1190          // If any of our suspense children toggle visibility, this means that1191          // the pending boundaries array needs to be updated, which we only1192          // do in the passive phase.1193          workInProgress.flags |= Passive;1194        }1195      }1196      return null;1197    }1198    case HostHoistable: {1199      // $FlowFixMe[constant-condition]1200      if (supportsResources) {1201        // The branching here is more complicated than you might expect because1202        // a HostHoistable sometimes corresponds to a Resource and sometimes1203        // corresponds to an Instance. It can also switch during an update.12041205        const type = workInProgress.type;1206        const nextResource: Resource | null = workInProgress.memoizedState;1207        if (current === null) {1208          // We are mounting and must Update this Hoistable in this commit1209          // @TODO refactor this block to create the instance here in complete1210          // phase if we are not hydrating.1211          markUpdate(workInProgress);1212          if (nextResource !== null) {1213            // This is a Hoistable Resource12141215            // This must come at the very end of the complete phase.1216            bubbleProperties(workInProgress);1217            preloadResourceAndSuspendIfNeeded(1218              workInProgress,1219              nextResource,1220              type,1221              newProps,1222              renderLanes,1223            );1224            return null;1225          } else {1226            // This is a Hoistable Instance1227            // This must come at the very end of the complete phase.1228            bubbleProperties(workInProgress);1229            preloadInstanceAndSuspendIfNeeded(1230              workInProgress,1231              type,1232              null,1233              newProps,1234              renderLanes,1235            );1236            return null;1237          }1238        } else {1239          // This is an update.1240          if (nextResource) {1241            // This is a Resource1242            if (nextResource !== current.memoizedState) {1243              // we have a new Resource. we need to update1244              markUpdate(workInProgress);1245              // This must come at the very end of the complete phase.1246              bubbleProperties(workInProgress);1247              // This must come at the very end of the complete phase, because it might1248              // throw to suspend, and if the resource immediately loads, the work loop1249              // will resume rendering as if the work-in-progress completed. So it must1250              // fully complete.1251              preloadResourceAndSuspendIfNeeded(1252                workInProgress,1253                nextResource,1254                type,1255                newProps,1256                renderLanes,1257              );1258              return null;1259            } else {1260              // This must come at the very end of the complete phase.1261              bubbleProperties(workInProgress);1262              workInProgress.flags &= ~MaySuspendCommit;1263              return null;1264            }1265          } else {1266            const oldProps = current.memoizedProps;1267            // This is an Instance1268            // We may have props to update on the Hoistable instance.1269            // $FlowFixMe[constant-condition]1270            if (supportsMutation) {1271              if (oldProps !== newProps) {1272                markUpdate(workInProgress);1273              }1274            } else {1275              // We use the updateHostComponent path because it produces1276              // the update queue we need for Hoistables.1277              updateHostComponent(1278                current,1279                workInProgress,1280                type,1281                newProps,1282                renderLanes,1283              );1284            }1285            // This must come at the very end of the complete phase.1286            bubbleProperties(workInProgress);1287            preloadInstanceAndSuspendIfNeeded(1288              workInProgress,1289              type,1290              oldProps,1291              newProps,1292              renderLanes,1293            );1294            return null;1295          }1296        }1297      }1298      // Fall through1299    }1300    case HostSingleton: {1301      // $FlowFixMe[constant-condition]1302      if (supportsSingletons) {1303        popHostContext(workInProgress);1304        const rootContainerInstance = getRootHostContainer();1305        const type = workInProgress.type;1306        if (current !== null && workInProgress.stateNode != null) {1307          // $FlowFixMe[constant-condition]1308          if (supportsMutation) {1309            const oldProps = current.memoizedProps;1310            if (oldProps !== newProps) {1311              markUpdate(workInProgress);1312            }1313          } else {1314            updateHostComponent(1315              current,1316              workInProgress,1317              type,1318              newProps,1319              renderLanes,1320            );1321          }1322        } else {1323          if (!newProps) {1324            if (workInProgress.stateNode === null) {1325              throw new Error(1326                'We must have new props for new mounts. This error is likely ' +1327                  'caused by a bug in React. Please file an issue.',1328              );1329            }13301331            // This can happen when we abort work.1332            bubbleProperties(workInProgress);1333            if (enableViewTransition) {1334              // Host Components act as their own View Transitions which doesn't run enter/exit animations.1335              // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1336              workInProgress.subtreeFlags &= ~ViewTransitionStatic;1337            }1338            return null;1339          }13401341          const currentHostContext = getHostContext();1342          const wasHydrated = popHydrationState(workInProgress);1343          let instance: Instance;1344          if (wasHydrated) {1345            // We ignore the boolean indicating there is an updateQueue because1346            // it is used only to set text children and HostSingletons do not1347            // use them.1348            prepareToHydrateHostInstance(workInProgress, currentHostContext);1349            instance = workInProgress.stateNode;1350          } else {1351            instance = resolveSingletonInstance(1352              type,1353              newProps,1354              rootContainerInstance,1355              currentHostContext,1356              true,1357            );1358            workInProgress.stateNode = instance;1359            markUpdate(workInProgress);1360          }1361        }1362        bubbleProperties(workInProgress);1363        if (enableViewTransition) {1364          // Host Components act as their own View Transitions which doesn't run enter/exit animations.1365          // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1366          workInProgress.subtreeFlags &= ~ViewTransitionStatic;1367        }1368        return null;1369      }1370      // Fall through1371    }1372    case HostComponent: {1373      popHostContext(workInProgress);1374      const type = workInProgress.type;1375      if (current !== null && workInProgress.stateNode != null) {1376        updateHostComponent(1377          current,1378          workInProgress,1379          type,1380          newProps,1381          renderLanes,1382        );1383      } else {1384        if (!newProps) {1385          if (workInProgress.stateNode === null) {1386            throw new Error(1387              'We must have new props for new mounts. This error is likely ' +1388                'caused by a bug in React. Please file an issue.',1389            );1390          }13911392          // This can happen when we abort work.1393          bubbleProperties(workInProgress);1394          if (enableViewTransition) {1395            // Host Components act as their own View Transitions which doesn't run enter/exit animations.1396            // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1397            workInProgress.subtreeFlags &= ~ViewTransitionStatic;1398          }1399          return null;1400        }14011402        const currentHostContext = getHostContext();1403        // TODO: Move createInstance to beginWork and keep it on a context1404        // "stack" as the parent. Then append children as we go in beginWork1405        // or completeWork depending on whether we want to add them top->down or1406        // bottom->up. Top->down is faster in IE11.1407        const wasHydrated = popHydrationState(workInProgress);1408        if (wasHydrated) {1409          // TODO: Move this and createInstance step into the beginPhase1410          // to consolidate.1411          prepareToHydrateHostInstance(workInProgress, currentHostContext);1412          if (1413            finalizeHydratedChildren(1414              workInProgress.stateNode,1415              type,1416              newProps,1417              currentHostContext,1418            )1419          ) {1420            workInProgress.flags |= Hydrate;1421          }1422        } else {1423          const rootContainerInstance = getRootHostContainer();1424          const instance = createInstance(1425            type,1426            newProps,1427            rootContainerInstance,1428            currentHostContext,1429            workInProgress,1430          );1431          // TODO: For persistent renderers, we should pass children as part1432          // of the initial instance creation1433          markCloned(workInProgress);1434          appendAllChildren(instance, workInProgress, false, false);1435          workInProgress.stateNode = instance;14361437          // Certain renderers require commit-time effects for initial mount.1438          // (eg DOM renderer supports auto-focus for certain elements).1439          // Make sure such renderers get scheduled for later work.1440          if (1441            finalizeInitialChildren(1442              instance,1443              type,1444              newProps,1445              currentHostContext,1446            )1447          ) {1448            markUpdate(workInProgress);1449          }1450        }1451      }1452      bubbleProperties(workInProgress);1453      if (enableViewTransition) {1454        // Host Components act as their own View Transitions which doesn't run enter/exit animations.1455        // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1456        workInProgress.subtreeFlags &= ~ViewTransitionStatic;1457      }14581459      // This must come at the very end of the complete phase, because it might1460      // throw to suspend, and if the resource immediately loads, the work loop1461      // will resume rendering as if the work-in-progress completed. So it must1462      // fully complete.1463      preloadInstanceAndSuspendIfNeeded(1464        workInProgress,1465        workInProgress.type,1466        current === null ? null : current.memoizedProps,1467        workInProgress.pendingProps,1468        renderLanes,1469      );1470      return null;1471    }1472    case HostText: {1473      const newText = newProps;1474      if (current && workInProgress.stateNode != null) {1475        const oldText = current.memoizedProps;1476        // If we have an alternate, that means this is an update and we need1477        // to schedule a side-effect to do the updates.1478        updateHostText(current, workInProgress, oldText, newText);1479      } else {1480        if (typeof newText !== 'string') {1481          if (workInProgress.stateNode === null) {1482            throw new Error(1483              'We must have new props for new mounts. This error is likely ' +1484                'caused by a bug in React. Please file an issue.',1485            );1486          }1487          // This can happen when we abort work.1488        }1489        const rootContainerInstance = getRootHostContainer();1490        const currentHostContext = getHostContext();1491        const wasHydrated = popHydrationState(workInProgress);1492        if (wasHydrated) {1493          prepareToHydrateHostTextInstance(workInProgress);1494        } else {1495          markCloned(workInProgress);1496          workInProgress.stateNode = createTextInstance(1497            newText,1498            rootContainerInstance,1499            currentHostContext,1500            workInProgress,1501          );1502        }1503      }1504      bubbleProperties(workInProgress);1505      return null;1506    }1507    case ActivityComponent: {1508      const nextState: null | ActivityState = workInProgress.memoizedState;15091510      if (current === null || current.memoizedState !== null) {1511        const fallthroughToNormalOffscreenPath =1512          completeDehydratedActivityBoundary(1513            current,1514            workInProgress,1515            nextState,1516          );1517        if (!fallthroughToNormalOffscreenPath) {1518          if (workInProgress.flags & ForceClientRender) {1519            popSuspenseHandler(workInProgress);1520            // Special case. There were remaining unhydrated nodes. We treat1521            // this as a mismatch. Revert to client rendering.1522            return workInProgress;1523          } else {1524            popSuspenseHandler(workInProgress);1525            // Did not finish hydrating, either because this is the initial1526            // render or because something suspended.1527            return null;1528          }1529        }15301531        if ((workInProgress.flags & DidCapture) !== NoFlags) {1532          // We called retryActivityComponentWithoutHydrating and tried client rendering1533          // but now we suspended again. We should never arrive here because we should1534          // not have pushed a suspense handler during that second pass and it should1535          // instead have suspended above.1536          throw new Error(1537            'Client rendering an Activity suspended it again. This is a bug in React.',1538          );1539        }15401541        // Continue with the normal Activity path.1542      }15431544      bubbleProperties(workInProgress);1545      return null;1546    }1547    case SuspenseComponent: {1548      const nextState: null | SuspenseState = workInProgress.memoizedState;15491550      // Special path for dehydrated boundaries. We may eventually move this1551      // to its own fiber type so that we can add other kinds of hydration1552      // boundaries that aren't associated with a Suspense tree. In anticipation1553      // of such a refactor, all the hydration logic is contained in1554      // this branch.1555      if (1556        current === null ||1557        (current.memoizedState !== null &&1558          current.memoizedState.dehydrated !== null)1559      ) {1560        const fallthroughToNormalSuspensePath =1561          completeDehydratedSuspenseBoundary(1562            current,1563            workInProgress,1564            nextState,1565          );1566        if (!fallthroughToNormalSuspensePath) {1567          if (workInProgress.flags & ForceClientRender) {1568            popSuspenseHandler(workInProgress);1569            // Special case. There were remaining unhydrated nodes. We treat1570            // this as a mismatch. Revert to client rendering.1571            return workInProgress;1572          } else {1573            popSuspenseHandler(workInProgress);1574            // Did not finish hydrating, either because this is the initial1575            // render or because something suspended.1576            return null;1577          }1578        }15791580        // Continue with the normal Suspense path.1581      }15821583      popSuspenseHandler(workInProgress);15841585      if ((workInProgress.flags & DidCapture) !== NoFlags) {1586        // Something suspended. Re-render with the fallback children.1587        workInProgress.lanes = renderLanes;1588        if (1589          enableProfilerTimer &&1590          (workInProgress.mode & ProfileMode) !== NoMode1591        ) {1592          transferActualDuration(workInProgress);1593        }1594        // Don't bubble properties in this case.1595        return workInProgress;1596      }15971598      const nextDidTimeout = nextState !== null;1599      const prevDidTimeout =1600        current !== null &&1601        (current.memoizedState as null | SuspenseState) !== null;16021603      if (nextDidTimeout) {1604        const offscreenFiber: Fiber = workInProgress.child as any;1605        let previousCache: Cache | null = null;1606        if (1607          offscreenFiber.alternate !== null &&1608          offscreenFiber.alternate.memoizedState !== null &&1609          offscreenFiber.alternate.memoizedState.cachePool !== null1610        ) {1611          previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;1612        }1613        let cache: Cache | null = null;1614        if (1615          offscreenFiber.memoizedState !== null &&1616          offscreenFiber.memoizedState.cachePool !== null1617        ) {1618          cache = offscreenFiber.memoizedState.cachePool.pool;1619        }1620        if (cache !== previousCache) {1621          // Run passive effects to retain/release the cache.1622          offscreenFiber.flags |= Passive;1623        }1624      }16251626      // If the suspended state of the boundary changes, we need to schedule1627      // a passive effect, which is when we process the transitions1628      if (nextDidTimeout !== prevDidTimeout) {1629        if (enableTransitionTracing) {1630          const offscreenFiber: Fiber = workInProgress.child as any;1631          offscreenFiber.flags |= Passive;1632        }16331634        // If the suspended state of the boundary changes, we need to schedule1635        // an effect to toggle the subtree's visibility. When we switch from1636        // fallback -> primary, the inner Offscreen fiber schedules this effect1637        // as part of its normal complete phase. But when we switch from1638        // primary -> fallback, the inner Offscreen fiber does not have a complete1639        // phase. So we need to schedule its effect here.1640        //1641        // We also use this flag to connect/disconnect the effects, but the same1642        // logic applies: when re-connecting, the Offscreen fiber's complete1643        // phase will handle scheduling the effect. It's only when the fallback1644        // is active that we have to do anything special.1645        if (nextDidTimeout) {1646          const offscreenFiber: Fiber = workInProgress.child as any;1647          offscreenFiber.flags |= Visibility;1648        }1649      }16501651      const retryQueue: RetryQueue | null = workInProgress.updateQueue as any;1652      scheduleRetryEffect(workInProgress, retryQueue);16531654      if (1655        enableSuspenseCallback &&1656        workInProgress.updateQueue !== null &&1657        workInProgress.memoizedProps.suspenseCallback != null1658      ) {1659        // Always notify the callback1660        // TODO: Move to passive phase1661        workInProgress.flags |= Update;1662      }1663      bubbleProperties(workInProgress);1664      if (enableProfilerTimer) {1665        if ((workInProgress.mode & ProfileMode) !== NoMode) {1666          if (nextDidTimeout) {1667            // Don't count time spent in a timed out Suspense subtree as part of the base duration.1668            const primaryChildFragment = workInProgress.child;1669            if (primaryChildFragment !== null) {1670              // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1671              workInProgress.treeBaseDuration -=1672                primaryChildFragment.treeBaseDuration as any as number;1673            }1674          }1675        }1676      }1677      return null;1678    }1679    case HostPortal:1680      popHostContainer(workInProgress);1681      updateHostContainer(current, workInProgress);1682      if (current === null) {1683        preparePortalMount(workInProgress.stateNode.containerInfo);1684      }1685      workInProgress.flags |= PortalStatic;1686      bubbleProperties(workInProgress);1687      return null;1688    case ContextProvider:1689      // Pop provider fiber1690      const context: ReactContext<any> = workInProgress.type;1691      popProvider(context, workInProgress);1692      bubbleProperties(workInProgress);1693      return null;1694    case IncompleteClassComponent: {1695      if (disableLegacyMode) {1696        break;1697      }1698      // Same as class component case. I put it down here so that the tags are1699      // sequential to ensure this switch is compiled to a jump table.1700      const Component = workInProgress.type;1701      if (isLegacyContextProvider(Component)) {1702        popLegacyContext(workInProgress);1703      }1704      bubbleProperties(workInProgress);1705      return null;1706    }1707    case SuspenseListComponent: {1708      popSuspenseListContext(workInProgress);17091710      const renderState: null | SuspenseListRenderState =1711        workInProgress.memoizedState;17121713      if (renderState === null) {1714        // We're running in the default, "independent" mode.1715        // We don't do anything in this mode.1716        bubbleProperties(workInProgress);1717        return null;1718      }17191720      let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;17211722      const renderedTail = renderState.rendering;1723      if (renderedTail === null) {1724        // We just rendered the head.1725        if (!didSuspendAlready) {1726          // This is the first pass. We need to figure out if anything is still1727          // suspended in the rendered set.17281729          // If new content unsuspended, but there's still some content that1730          // didn't. Then we need to do a second pass that forces everything1731          // to keep showing their fallbacks.17321733          // We might be suspended if something in this render pass suspended, or1734          // something in the previous committed pass suspended. Otherwise,1735          // there's no chance so we can skip the expensive call to1736          // findFirstSuspended.1737          const cannotBeSuspended =1738            renderHasNotSuspendedYet() &&1739            (current === null || (current.flags & DidCapture) === NoFlags);1740          if (!cannotBeSuspended) {1741            let row = workInProgress.child;1742            while (row !== null) {1743              const suspended = findFirstSuspended(row);1744              if (suspended !== null) {1745                didSuspendAlready = true;1746                workInProgress.flags |= DidCapture;1747                cutOffTailIfNeeded(renderState, false);17481749                // If this is a newly suspended tree, it might not get committed as1750                // part of the second pass. In that case nothing will subscribe to1751                // its thenables. Instead, we'll transfer its thenables to the1752                // SuspenseList so that it can retry if they resolve.1753                // There might be multiple of these in the list but since we're1754                // going to wait for all of them anyway, it doesn't really matter1755                // which ones gets to ping. In theory we could get clever and keep1756                // track of how many dependencies remain but it gets tricky because1757                // in the meantime, we can add/remove/change items and dependencies.1758                // We might bail out of the loop before finding any but that1759                // doesn't matter since that means that the other boundaries that1760                // we did find already has their listeners attached.1761                const retryQueue: RetryQueue | null =1762                  suspended.updateQueue as any;1763                workInProgress.updateQueue = retryQueue;1764                scheduleRetryEffect(workInProgress, retryQueue);17651766                // Rerender the whole list, but this time, we'll force fallbacks1767                // to stay in place.1768                // Reset the effect flags before doing the second pass since that's now invalid.1769                // Reset the child fibers to their original state.1770                workInProgress.subtreeFlags = NoFlags;1771                resetChildFibers(workInProgress, renderLanes);17721773                // Set up the Suspense List Context to force suspense and1774                // immediately rerender the children.1775                pushSuspenseListContext(1776                  workInProgress,1777                  setShallowSuspenseListContext(1778                    suspenseStackCursor.current,1779                    ForceSuspenseFallback,1780                  ),1781                );1782                if (getIsHydrating()) {1783                  // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1784                  pushTreeFork(workInProgress, renderState.treeForkCount);1785                }1786                // Don't bubble properties in this case.1787                return workInProgress.child;1788              }1789              row = row.sibling;1790            }1791          }17921793          if (renderState.tail !== null && now() > getRenderTargetTime()) {1794            // We have already passed our CPU deadline but we still have rows1795            // left in the tail. We'll just give up further attempts to render1796            // the main content and only render fallbacks.1797            workInProgress.flags |= DidCapture;1798            didSuspendAlready = true;17991800            cutOffTailIfNeeded(renderState, false);18011802            // Since nothing actually suspended, there will nothing to ping this1803            // to get it started back up to attempt the next item. While in terms1804            // of priority this work has the same priority as this current render,1805            // it's not part of the same transition once the transition has1806            // committed. If it's sync, we still want to yield so that it can be1807            // painted. Conceptually, this is really the same as pinging.1808            // We can use any RetryLane even if it's the one currently rendering1809            // since we're leaving it behind on this node.1810            workInProgress.lanes = SomeRetryLane;1811          }1812        } else {1813          cutOffTailIfNeeded(renderState, false);1814        }1815        // Next we're going to render the tail.1816      } else {1817        // Append the rendered row to the child list.1818        if (!didSuspendAlready) {1819          const suspended = findFirstSuspended(renderedTail);1820          if (suspended !== null) {1821            workInProgress.flags |= DidCapture;1822            didSuspendAlready = true;18231824            // Ensure we transfer the update queue to the parent so that it doesn't1825            // get lost if this row ends up dropped during a second pass.1826            const retryQueue: RetryQueue | null = suspended.updateQueue as any;1827            workInProgress.updateQueue = retryQueue;1828            scheduleRetryEffect(workInProgress, retryQueue);18291830            cutOffTailIfNeeded(renderState, true);1831            // This might have been modified.1832            if (1833              renderState.tail === null &&1834              renderState.tailMode !== 'collapsed' &&1835              renderState.tailMode !== 'visible' &&1836              !renderedTail.alternate &&1837              !getIsHydrating() // We don't cut it if we're hydrating.1838            ) {1839              // We're done.1840              bubbleProperties(workInProgress);1841              return null;1842            }1843          } else if (1844            // The time it took to render last row is greater than the remaining1845            // time we have to render. So rendering one more row would likely1846            // exceed it.1847            now() * 2 - renderState.renderingStartTime >1848              getRenderTargetTime() &&1849            renderLanes !== OffscreenLane1850          ) {1851            // We have now passed our CPU deadline and we'll just give up further1852            // attempts to render the main content and only render fallbacks.1853            // The assumption is that this is usually faster.1854            workInProgress.flags |= DidCapture;1855            didSuspendAlready = true;18561857            cutOffTailIfNeeded(renderState, false);18581859            // Since nothing actually suspended, there will nothing to ping this1860            // to get it started back up to attempt the next item. While in terms1861            // of priority this work has the same priority as this current render,1862            // it's not part of the same transition once the transition has1863            // committed. If it's sync, we still want to yield so that it can be1864            // painted. Conceptually, this is really the same as pinging.1865            // We can use any RetryLane even if it's the one currently rendering1866            // since we're leaving it behind on this node.1867            workInProgress.lanes = SomeRetryLane;1868          }1869        }1870        if (renderState.isBackwards) {1871          // Append to the beginning of the list.1872          renderedTail.sibling = workInProgress.child;1873          workInProgress.child = renderedTail;1874        } else {1875          const previousSibling = renderState.last;1876          if (previousSibling !== null) {1877            previousSibling.sibling = renderedTail;1878          } else {1879            workInProgress.child = renderedTail;1880          }1881          renderState.last = renderedTail;1882        }1883      }18841885      if (renderState.tail !== null) {1886        // We still have tail rows to render.1887        // Pop a row.1888        // TODO: Consider storing the first of the new mount tail in the state so1889        // that we don't have to recompute this for every row in the list.1890        const next = renderState.tail;1891        const onlyNewMounts = isOnlyNewMounts(next);1892        renderState.rendering = next;1893        renderState.tail = next.sibling;1894        renderState.renderingStartTime = now();1895        next.sibling = null;18961897        // Restore the context.1898        // TODO: We can probably just avoid popping it instead and only1899        // setting it the first time we go from not suspended to suspended.1900        let suspenseContext = suspenseStackCursor.current;1901        if (didSuspendAlready) {1902          suspenseContext = setShallowSuspenseListContext(1903            suspenseContext,1904            ForceSuspenseFallback,1905          );1906        } else {1907          suspenseContext =1908            setDefaultShallowSuspenseListContext(suspenseContext);1909        }1910        if (1911          renderState.tailMode === 'visible' ||1912          renderState.tailMode === 'collapsed' ||1913          !onlyNewMounts ||1914          // TODO: While hydrating, we still let it suspend the parent. Tail mode hidden has broken1915          // hydration anyway right now but this preserves the previous semantics out of caution.1916          // Once proper hydration is implemented, this special case should be removed as it should1917          // never be needed.1918          getIsHydrating()1919        ) {1920          pushSuspenseListContext(workInProgress, suspenseContext);1921        } else {1922          // If we are rendering in 'hidden' (default) tail mode, then we if we suspend in the1923          // tail itself, we can delete it rather than suspend the parent. So we act as a catch in that1924          // case. For 'collapsed' we need to render at least one in suspended state, after which we'll1925          // have cut off the rest to never attempt it so it never hits this case.1926          // If this is an updated node, we cannot delete it from the tail so it's effectively visible.1927          // As a consequence, if it resuspends it actually suspends the parent by taking the other path.1928          pushSuspenseListCatch(workInProgress, suspenseContext);1929        }1930        // Do a pass over the next row.1931        if (getIsHydrating()) {1932          // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1933          pushTreeFork(workInProgress, renderState.treeForkCount);1934        }1935        // Don't bubble properties in this case.1936        return next;1937      }1938      bubbleProperties(workInProgress);1939      return null;1940    }1941    case ScopeComponent: {1942      if (enableScopeAPI) {1943        if (current === null) {1944          const scopeInstance: ReactScopeInstance = createScopeInstance();1945          workInProgress.stateNode = scopeInstance;1946          prepareScopeUpdate(scopeInstance, workInProgress);1947          if (workInProgress.ref !== null) {1948            // Scope components always do work in the commit phase if there's a1949            // ref attached.1950            markUpdate(workInProgress);1951          }1952        } else {1953          if (workInProgress.ref !== null) {1954            // Scope components always do work in the commit phase if there's a1955            // ref attached.1956            markUpdate(workInProgress);1957          }1958        }1959        bubbleProperties(workInProgress);1960        return null;1961      }1962      break;1963    }1964    case OffscreenComponent:1965    case LegacyHiddenComponent: {1966      popSuspenseHandler(workInProgress);1967      popHiddenContext(workInProgress);1968      const nextState: OffscreenState | null = workInProgress.memoizedState;1969      const nextIsHidden = nextState !== null;19701971      // Schedule a Visibility effect if the visibility has changed1972      if (enableLegacyHidden && workInProgress.tag === LegacyHiddenComponent) {1973        // LegacyHidden doesn't do any hiding — it only pre-renders.1974      } else {1975        if (current !== null) {1976          const prevState: OffscreenState | null = current.memoizedState;1977          const prevIsHidden = prevState !== null;1978          if (prevIsHidden !== nextIsHidden) {1979            workInProgress.flags |= Visibility;1980          }1981        } else {1982          // On initial mount, we only need a Visibility effect if the tree1983          // is hidden.1984          if (nextIsHidden) {1985            workInProgress.flags |= Visibility;1986          }1987        }1988      }19891990      if (1991        !nextIsHidden ||1992        (!disableLegacyMode &&1993          (workInProgress.mode & ConcurrentMode) === NoMode)1994      ) {1995        bubbleProperties(workInProgress);1996      } else {1997        // Don't bubble properties for hidden children unless we're rendering1998        // at offscreen priority.1999        if (2000          includesSomeLane(renderLanes, OffscreenLane as Lane) &&

Code quality findings 100

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const didBailout = current !== null && current.child === completedWork.child;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((completedWork.flags & ChildDeletion) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(child.flags & checkedFlags) !== NoFlags ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(child.subtreeFlags & checkedFlags) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.tag === HostComponent || node.tag === HostText) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
node.tag === HostPortal ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(supportsSingletons ? node.tag === HostSingleton : false)
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node.sibling === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.return === null || node.return === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.tag === HostComponent) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.tag === HostText) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.tag === HostPortal) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
node.tag === OffscreenComponent &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
node.memoizedState !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node.sibling === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.return === null || node.return === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.tag === HostComponent) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.tag === HostText) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.tag === HostPortal) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
node.tag === OffscreenComponent &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
node.memoizedState !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (node.child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (node.sibling === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node.return === null || node.return === workInProgress) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (oldProps === newProps) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (!requiresClone && oldProps === newProps) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (newInstance === currentInstance) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(oldProps === null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (wakeables !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
workInProgress.tag !== OffscreenComponent
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (oldText !== newText) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (oldText !== newText) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (renderState.tailMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (tailNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (tailNode.alternate !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (lastTailNode === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (!hasRenderedATailFallback && renderState.tail !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (tailNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (tailNode.alternate !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (lastTailNode === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (fiber !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (fiber.alternate !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
completedWork.alternate !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
completedWork.alternate.child === completedWork.child;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextState !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isTimedOutSuspense = nextState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (primaryChildFragment !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.flags & DidCapture) === NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isTimedOutSuspense = nextState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (primaryChildFragment !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null && current.memoizedState !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextState !== null && nextState.dehydrated !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isTimedOutSuspense = nextState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (primaryChildFragment !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.flags & DidCapture) === NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.mode & ProfileMode) !== NoMode) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isTimedOutSuspense = nextState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (primaryChildFragment !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null && current.memoizedState !== null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (workInProgress.tag) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (transitions !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (cache !== previousCache) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null || current.child === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(workInProgress.flags & ForceClientRender) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextResource !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextResource !== current.memoizedState) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (oldProps !== newProps) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null && workInProgress.stateNode != null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (current !== null && workInProgress.stateNode != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (oldProps !== newProps) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (workInProgress.stateNode === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current !== null && workInProgress.stateNode != null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (current !== null && workInProgress.stateNode != null) {

Get this view in your editor

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