packages/react-reconciler/src/ReactFiberCommitWork.js JAVASCRIPT 5,383 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,383.
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 {11  Instance,12  TextInstance,13  ActivityInstance,14  SuspenseInstance,15  Container,16  HoistableRoot,17  FormInstance,18  Props,19  SuspendedState,20} from './ReactFiberConfig';21import type {Fiber, FiberRoot} from './ReactInternalTypes';22import type {Lanes} from './ReactFiberLane';23import {24  includesLoadingIndicatorLanes,25  includesOnlySuspenseyCommitEligibleLanes,26  includesOnlyViewTransitionEligibleLanes,27} from './ReactFiberLane';28import type {ActivityState} from './ReactFiberActivityComponent';29import type {SuspenseState, RetryQueue} from './ReactFiberSuspenseComponent';30import type {UpdateQueue} from './ReactFiberClassUpdateQueue';31import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';32import type {Wakeable, ViewTransitionProps} from 'shared/ReactTypes';33import type {34  OffscreenState,35  OffscreenInstance,36  OffscreenQueue,37} from './ReactFiberOffscreenComponent';38import type {Cache} from './ReactFiberCacheComponent';39import type {RootState} from './ReactFiberRoot';40import type {Transition} from 'react/src/ReactStartTransition';41import type {42  TracingMarkerInstance,43  TransitionAbort,44} from './ReactFiberTracingMarkerComponent';45import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';4647import {48  alwaysThrottleRetries,49  enableCreateEventHandleAPI,50  enableEffectEventMutationPhase,51  enableProfilerTimer,52  enableProfilerCommitHooks,53  enableSuspenseCallback,54  enableScopeAPI,55  enableUpdaterTracking,56  enableTransitionTracing,57  enableLegacyHidden,58  disableLegacyMode,59  enableComponentPerformanceTrack,60  enableViewTransition,61  enableFragmentRefs,62  enableDefaultTransitionIndicator,63  enableFragmentRefsTextNodes,64} from 'shared/ReactFeatureFlags';65import {66  FunctionComponent,67  ForwardRef,68  ClassComponent,69  HostRoot,70  HostComponent,71  HostHoistable,72  HostSingleton,73  HostText,74  HostPortal,75  Profiler,76  ActivityComponent,77  SuspenseComponent,78  DehydratedFragment,79  IncompleteClassComponent,80  MemoComponent,81  SimpleMemoComponent,82  SuspenseListComponent,83  ScopeComponent,84  OffscreenComponent,85  LegacyHiddenComponent,86  CacheComponent,87  TracingMarkerComponent,88  ViewTransitionComponent,89  Fragment,90} from './ReactWorkTags';91import {92  NoFlags,93  ContentReset,94  Placement,95  ChildDeletion,96  Snapshot,97  Update,98  Hydrate,99  Callback,100  Ref,101  Hydrating,102  Passive,103  BeforeMutationMask,104  BeforeAndAfterMutationTransitionMask,105  MutationMask,106  LayoutMask,107  PassiveMask,108  PassiveTransitionMask,109  Visibility,110  ShouldSuspendCommit,111  MaySuspendCommit,112  FormReset,113  Cloned,114  PerformedWork,115  ForceClientRender,116  DidCapture,117  AffectedParentLayout,118  ViewTransitionNamedStatic,119  PortalStatic,120} from './ReactFiberFlags';121import {122  commitStartTime,123  pushNestedEffectDurations,124  popNestedEffectDurations,125  bubbleNestedEffectDurations,126  resetComponentEffectTimers,127  pushComponentEffectStart,128  popComponentEffectStart,129  pushComponentEffectDuration,130  popComponentEffectDuration,131  pushComponentEffectErrors,132  popComponentEffectErrors,133  pushComponentEffectDidSpawnUpdate,134  popComponentEffectDidSpawnUpdate,135  componentEffectStartTime,136  componentEffectEndTime,137  componentEffectDuration,138  componentEffectErrors,139  componentEffectSpawnedUpdate,140} from './ReactProfilerTimer';141import {142  logComponentRender,143  logComponentErrored,144  logComponentEffect,145  logComponentMount,146  logComponentUnmount,147  logComponentReappeared,148  logComponentDisappeared,149  pushDeepEquality,150  popDeepEquality,151} from './ReactFiberPerformanceTrack';152import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';153import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';154import {155  supportsMutation,156  supportsPersistence,157  supportsHydration,158  supportsResources,159  supportsSingletons,160  clearSuspenseBoundary,161  clearSuspenseBoundaryFromContainer,162  createContainerChildSet,163  clearContainer,164  prepareScopeUpdate,165  prepareForCommit,166  beforeActiveInstanceBlur,167  detachDeletedInstance,168  getHoistableRoot,169  acquireResource,170  releaseResource,171  hydrateHoistable,172  mountHoistable,173  unmountHoistable,174  prepareToCommitHoistables,175  maySuspendCommitInSyncRender,176  suspendInstance,177  suspendResource,178  resetFormInstance,179  registerSuspenseInstanceRetry,180  cancelViewTransitionName,181  cancelRootViewTransitionName,182  restoreRootViewTransitionName,183  isSingletonScope,184  updateFragmentInstanceFiber,185} from './ReactFiberConfig';186import {187  captureCommitPhaseError,188  resolveRetryWakeable,189  markCommitTimeOfFallback,190  restorePendingUpdaters,191  addTransitionStartCallbackToPendingTransition,192  addTransitionProgressCallbackToPendingTransition,193  addTransitionCompleteCallbackToPendingTransition,194  addMarkerProgressCallbackToPendingTransition,195  addMarkerIncompleteCallbackToPendingTransition,196  addMarkerCompleteCallbackToPendingTransition,197  retryDehydratedSuspenseBoundary,198  scheduleViewTransitionEvent,199} from './ReactFiberWorkLoop';200import {201  HasEffect as HookHasEffect,202  Layout as HookLayout,203  Insertion as HookInsertion,204  Passive as HookPassive,205} from './ReactHookEffectTags';206import {doesFiberContain} from './ReactFiberTreeReflection';207import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';208import {releaseCache, retainCache} from './ReactFiberCacheComponent';209import {clearTransitionsForLanes} from './ReactFiberLane';210import {211  OffscreenVisible,212  OffscreenPassiveEffectsConnected,213} from './ReactFiberOffscreenComponent';214import {215  TransitionRoot,216  TransitionTracingMarker,217} from './ReactFiberTracingMarkerComponent';218import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';219import {220  commitHookLayoutEffects,221  commitHookLayoutUnmountEffects,222  commitHookEffectListMount,223  commitHookEffectListUnmount,224  commitHookPassiveMountEffects,225  commitHookPassiveUnmountEffects,226  commitClassLayoutLifecycles,227  commitClassDidMount,228  commitClassCallbacks,229  commitClassHiddenCallbacks,230  commitClassSnapshot,231  safelyCallComponentWillUnmount,232  safelyAttachRef,233  safelyDetachRef,234  commitProfilerUpdate,235  commitProfilerPostCommit,236  commitRootCallbacks,237} from './ReactFiberCommitEffects';238import {239  commitHostMount,240  commitHostHydratedInstance,241  commitHostUpdate,242  commitHostTextUpdate,243  commitHostResetTextContent,244  commitShowHideSuspenseBoundary,245  commitShowHideHostInstance,246  commitShowHideHostTextInstance,247  commitHostPlacement,248  commitHostRootContainerChildren,249  commitHostPortalContainerChildren,250  commitHostHydratedContainer,251  commitHostHydratedActivity,252  commitHostHydratedSuspense,253  commitHostRemoveChildFromContainer,254  commitHostRemoveChild,255  commitHostSingletonAcquisition,256  commitHostSingletonRelease,257  commitFragmentInstanceDeletionEffects,258  commitFragmentInstanceInsertionEffects,259} from './ReactFiberCommitHostEffects';260import {261  trackEnterViewTransitions,262  commitEnterViewTransitions,263  commitExitViewTransitions,264  commitBeforeUpdateViewTransition,265  commitNestedViewTransitions,266  restoreEnterOrExitViewTransitions,267  restoreUpdateViewTransition,268  restoreNestedViewTransitions,269  measureUpdateViewTransition,270  measureNestedViewTransitions,271  resetAppearingViewTransitions,272  trackAppearingViewTransition,273  viewTransitionCancelableChildren,274  pushViewTransitionCancelableScope,275  popViewTransitionCancelableScope,276} from './ReactFiberCommitViewTransitions';277import {278  viewTransitionMutationContext,279  pushRootMutationContext,280  pushMutationContext,281  popMutationContext,282  rootMutationContext,283} from './ReactFiberMutationTracking';284import {285  trackNamedViewTransition,286  untrackNamedViewTransition,287} from './ReactFiberDuplicateViewTransitions';288import {markIndicatorHandled} from './ReactFiberRootScheduler';289import type {Flags} from './ReactFiberFlags';290291// Used during the commit phase to track the state of the Offscreen component stack.292// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.293let offscreenSubtreeIsHidden: boolean = false;294let offscreenSubtreeWasHidden: boolean = false;295// Track whether there's a hidden offscreen above with no HostComponent between. If so,296// it overrides the hiddenness of the HostComponent below.297let offscreenDirectParentIsHidden: boolean = false;298299// Used to track if a form needs to be reset at the end of the mutation phase.300let needsFormReset = false;301302const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;303304let nextEffect: Fiber | null = null;305306// Used for Profiling builds to track updaters.307let inProgressLanes: Lanes | null = null;308let inProgressRoot: FiberRoot | null = null;309310let focusedInstanceHandle: null | Fiber = null;311export let shouldFireAfterActiveInstanceBlur: boolean = false;312313// Used during the commit phase to track whether a parent ViewTransition component314// might have been affected by any mutations / relayouts below.315let viewTransitionContextChanged: boolean = false;316let inUpdateViewTransition: boolean = false;317let rootViewTransitionAffected: boolean = false;318let rootViewTransitionNameCanceled: boolean = false;319320function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {321  if (finishedWork.tag === ActivityComponent) {322    const prevState: ActivityState | null = current.memoizedState;323    const nextState: ActivityState | null = finishedWork.memoizedState;324    return prevState !== null && nextState === null;325  } else if (finishedWork.tag === SuspenseComponent) {326    const prevState: SuspenseState | null = current.memoizedState;327    const nextState: SuspenseState | null = finishedWork.memoizedState;328    return (329      prevState !== null &&330      prevState.dehydrated !== null &&331      (nextState === null || nextState.dehydrated === null)332    );333  } else if (finishedWork.tag === HostRoot) {334    return (335      (current.memoizedState as RootState).isDehydrated &&336      (finishedWork.flags & ForceClientRender) === NoFlags337    );338  } else {339    return false;340  }341}342343export function commitBeforeMutationEffects(344  root: FiberRoot,345  firstChild: Fiber,346  committedLanes: Lanes,347): void {348  focusedInstanceHandle = prepareForCommit(root.containerInfo);349  shouldFireAfterActiveInstanceBlur = false;350351  const isViewTransitionEligible =352    enableViewTransition &&353    includesOnlyViewTransitionEligibleLanes(committedLanes);354355  nextEffect = firstChild;356  commitBeforeMutationEffects_begin(isViewTransitionEligible);357358  // We no longer need to track the active instance fiber359  focusedInstanceHandle = null;360  // We've found any matched pairs and can now reset.361  resetAppearingViewTransitions();362}363364function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {365  // If this commit is eligible for a View Transition we look into all mutated subtrees.366  // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.367  const subtreeMask = isViewTransitionEligible368    ? BeforeAndAfterMutationTransitionMask369    : BeforeMutationMask;370  while (nextEffect !== null) {371    const fiber = nextEffect;372373    // This phase is only used for beforeActiveInstanceBlur.374    // Let's skip the whole loop if it's off.375    if (enableCreateEventHandleAPI || isViewTransitionEligible) {376      // TODO: Should wrap this in flags check, too, as optimization377      const deletions = fiber.deletions;378      if (deletions !== null) {379        for (let i = 0; i < deletions.length; i++) {380          const deletion = deletions[i];381          commitBeforeMutationEffectsDeletion(382            deletion,383            isViewTransitionEligible,384          );385        }386      }387    }388389    if (390      enableViewTransition &&391      fiber.alternate === null &&392      (fiber.flags & Placement) !== NoFlags393    ) {394      // Skip before mutation effects of the children because we don't want395      // to trigger updates of any nested view transitions and we shouldn't396      // have any other before mutation effects since snapshot effects are397      // only applied to updates. TODO: Model this using only flags.398      if (isViewTransitionEligible) {399        trackEnterViewTransitions(fiber);400      }401      commitBeforeMutationEffects_complete(isViewTransitionEligible);402      continue;403    }404405    // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.406    if (enableViewTransition && fiber.tag === OffscreenComponent) {407      const isModernRoot =408        disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;409      if (isModernRoot) {410        const current = fiber.alternate;411        const isHidden = fiber.memoizedState !== null;412        if (isHidden) {413          if (414            current !== null &&415            current.memoizedState === null &&416            isViewTransitionEligible417          ) {418            // Was previously mounted as visible but is now hidden.419            commitExitViewTransitions(current);420          }421          // Skip before mutation effects of the children because they're hidden.422          commitBeforeMutationEffects_complete(isViewTransitionEligible);423          continue;424        } else if (current !== null && current.memoizedState !== null) {425          // Was previously mounted as hidden but is now visible.426          // Skip before mutation effects of the children because we don't want427          // to trigger updates of any nested view transitions and we shouldn't428          // have any other before mutation effects since snapshot effects are429          // only applied to updates. TODO: Model this using only flags.430          if (isViewTransitionEligible) {431            trackEnterViewTransitions(fiber);432          }433          commitBeforeMutationEffects_complete(isViewTransitionEligible);434          continue;435        }436      }437    }438439    const child = fiber.child;440    if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {441      child.return = fiber;442      nextEffect = child;443    } else {444      if (isViewTransitionEligible) {445        // We are inside an updated subtree. Any mutations that affected the446        // parent HostInstance's layout or set of children (such as reorders)447        // might have also affected the positioning or size of the inner448        // ViewTransitions. Therefore we need to find them inside.449        commitNestedViewTransitions(fiber);450      }451      commitBeforeMutationEffects_complete(isViewTransitionEligible);452    }453  }454}455456function commitBeforeMutationEffects_complete(457  isViewTransitionEligible: boolean,458) {459  while (nextEffect !== null) {460    const fiber = nextEffect;461    commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);462463    const sibling = fiber.sibling;464    if (sibling !== null) {465      sibling.return = fiber.return;466      nextEffect = sibling;467      return;468    }469470    nextEffect = fiber.return;471  }472}473474function commitBeforeMutationEffectsOnFiber(475  finishedWork: Fiber,476  isViewTransitionEligible: boolean,477) {478  const current = finishedWork.alternate;479  const flags = finishedWork.flags;480481  if (enableCreateEventHandleAPI) {482    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {483      // Check to see if the focused element was inside of a hidden (Suspense) subtree.484      // TODO: Move this out of the hot path using a dedicated effect tag.485      // TODO: This should consider Offscreen in general and not just SuspenseComponent.486      if (487        finishedWork.tag === SuspenseComponent &&488        isSuspenseBoundaryBeingHidden(current, finishedWork) &&489        // $FlowFixMe[incompatible-type] found when upgrading Flow490        doesFiberContain(finishedWork, focusedInstanceHandle)491      ) {492        shouldFireAfterActiveInstanceBlur = true;493        beforeActiveInstanceBlur(finishedWork);494      }495    }496  }497498  switch (finishedWork.tag) {499    case FunctionComponent:500    case ForwardRef:501    case SimpleMemoComponent: {502      if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {503        const updateQueue: FunctionComponentUpdateQueue | null =504          finishedWork.updateQueue as any;505        const eventPayloads = updateQueue !== null ? updateQueue.events : null;506        if (eventPayloads !== null) {507          for (let ii = 0; ii < eventPayloads.length; ii++) {508            const {ref, nextImpl} = eventPayloads[ii];509            ref.impl = nextImpl;510          }511        }512      }513      break;514    }515    case ClassComponent: {516      if ((flags & Snapshot) !== NoFlags) {517        if (current !== null) {518          commitClassSnapshot(finishedWork, current);519        }520      }521      break;522    }523    case HostRoot: {524      if ((flags & Snapshot) !== NoFlags) {525        // $FlowFixMe[constant-condition]526        if (supportsMutation) {527          const root = finishedWork.stateNode;528          clearContainer(root.containerInfo);529        }530      }531      break;532    }533    case HostComponent:534    case HostHoistable:535    case HostSingleton:536    case HostText:537    case HostPortal:538    case IncompleteClassComponent:539      // Nothing to do for these component types540      break;541    case ViewTransitionComponent:542      if (enableViewTransition) {543        if (isViewTransitionEligible) {544          if (current === null) {545            // This is a new mount. We should have handled this as part of the546            // Placement effect or it is deeper inside a entering transition.547          } else {548            // Something may have mutated within this subtree. This might need to cause549            // a cross-fade of this parent. We first assign old names to the550            // previous tree in the before mutation phase in case we need to.551            // TODO: This walks the tree that we might continue walking anyway.552            // We should just stash the parent ViewTransitionComponent and continue553            // walking the tree until we find HostComponent but to do that we need554            // to use a stack which requires refactoring this phase.555            commitBeforeUpdateViewTransition(current, finishedWork);556          }557        }558        break;559      }560    // Fallthrough561    default: {562      if ((flags & Snapshot) !== NoFlags) {563        throw new Error(564          'This unit of work tag should not have side-effects. This error is ' +565            'likely caused by a bug in React. Please file an issue.',566        );567      }568    }569  }570}571572function commitBeforeMutationEffectsDeletion(573  deletion: Fiber,574  isViewTransitionEligible: boolean,575) {576  if (enableCreateEventHandleAPI) {577    // TODO (effects) It would be nice to avoid calling doesFiberContain()578    // Maybe we can repurpose one of the subtreeFlags positions for this instead?579    // Use it to store which part of the tree the focused instance is in?580    // This assumes we can safely determine that instance during the "render" phase.581    if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {582      shouldFireAfterActiveInstanceBlur = true;583      beforeActiveInstanceBlur(deletion);584    }585  }586  if (isViewTransitionEligible) {587    commitExitViewTransitions(deletion);588  }589}590591function commitLayoutEffectOnFiber(592  finishedRoot: FiberRoot,593  current: Fiber | null,594  finishedWork: Fiber,595  committedLanes: Lanes,596): void {597  const prevEffectStart = pushComponentEffectStart();598  const prevEffectDuration = pushComponentEffectDuration();599  const prevEffectErrors = pushComponentEffectErrors();600  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();601  // When updating this function, also update reappearLayoutEffects, which does602  // most of the same things when an offscreen tree goes from hidden -> visible.603  const flags = finishedWork.flags;604  switch (finishedWork.tag) {605    case FunctionComponent:606    case ForwardRef:607    case SimpleMemoComponent: {608      recursivelyTraverseLayoutEffects(609        finishedRoot,610        finishedWork,611        committedLanes,612      );613      if (flags & Update) {614        commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);615      }616      break;617    }618    case ClassComponent: {619      recursivelyTraverseLayoutEffects(620        finishedRoot,621        finishedWork,622        committedLanes,623      );624      if (flags & Update) {625        commitClassLayoutLifecycles(finishedWork, current);626      }627628      if (flags & Callback) {629        commitClassCallbacks(finishedWork);630      }631632      if (flags & Ref) {633        safelyAttachRef(finishedWork, finishedWork.return);634      }635      break;636    }637    case HostRoot: {638      const prevProfilerEffectDuration = pushNestedEffectDurations();639      recursivelyTraverseLayoutEffects(640        finishedRoot,641        finishedWork,642        committedLanes,643      );644      if (flags & Callback) {645        commitRootCallbacks(finishedWork);646      }647      if (enableProfilerTimer && enableProfilerCommitHooks) {648        finishedRoot.effectDuration += popNestedEffectDurations(649          prevProfilerEffectDuration,650        );651      }652      break;653    }654    case HostSingleton: {655      // $FlowFixMe[constant-condition]656      if (supportsSingletons) {657        // We acquire the singleton instance first so it has appropriate658        // styles before other layout effects run. This isn't perfect because659        // an early sibling of the singleton may have an effect that can660        // observe the singleton before it is acquired.661        // @TODO move this to the mutation phase. The reason it isn't there yet662        // is it seemingly requires an extra traversal because we need to move the663        // disappear effect into a phase before the appear phase664        if (current === null && flags & Update) {665          // Unlike in the reappear path we only acquire on new mount666          commitHostSingletonAcquisition(finishedWork);667        }668        // We fall through to the HostComponent case below.669      }670      // Fallthrough671    }672    case HostHoistable:673    case HostComponent: {674      recursivelyTraverseLayoutEffects(675        finishedRoot,676        finishedWork,677        committedLanes,678      );679680      // Renderers may schedule work to be done after host components are mounted681      // (eg DOM renderer may schedule auto-focus for inputs and form controls).682      // These effects should only be committed when components are first mounted,683      // aka when there is no current/alternate.684      if (current === null) {685        if (flags & Update) {686          commitHostMount(finishedWork);687        } else if (flags & Hydrate) {688          commitHostHydratedInstance(finishedWork);689        }690      }691692      if (flags & Ref) {693        safelyAttachRef(finishedWork, finishedWork.return);694      }695      break;696    }697    case Profiler: {698      // TODO: Should this fire inside an offscreen tree? Or should it wait to699      // fire when the tree becomes visible again.700      if (flags & Update) {701        const prevProfilerEffectDuration = pushNestedEffectDurations();702703        recursivelyTraverseLayoutEffects(704          finishedRoot,705          finishedWork,706          committedLanes,707        );708709        const profilerInstance = finishedWork.stateNode;710711        if (enableProfilerTimer && enableProfilerCommitHooks) {712          // Propagate layout effect durations to the next nearest Profiler ancestor.713          // Do not reset these values until the next render so DevTools has a chance to read them first.714          profilerInstance.effectDuration += bubbleNestedEffectDurations(715            prevProfilerEffectDuration,716          );717        }718719        commitProfilerUpdate(720          finishedWork,721          current,722          commitStartTime,723          profilerInstance.effectDuration,724        );725      } else {726        recursivelyTraverseLayoutEffects(727          finishedRoot,728          finishedWork,729          committedLanes,730        );731      }732      break;733    }734    case ActivityComponent: {735      recursivelyTraverseLayoutEffects(736        finishedRoot,737        finishedWork,738        committedLanes,739      );740      if (flags & Update) {741        commitActivityHydrationCallbacks(finishedRoot, finishedWork);742      }743      break;744    }745    case SuspenseComponent: {746      recursivelyTraverseLayoutEffects(747        finishedRoot,748        finishedWork,749        committedLanes,750      );751      if (flags & Update) {752        commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);753      }754      if (flags & Callback) {755        // This Boundary is in fallback and has a dehydrated Suspense instance.756        // We could in theory assume the dehydrated state but we recheck it for757        // certainty.758        const finishedState: SuspenseState | null = finishedWork.memoizedState;759        if (finishedState !== null) {760          const dehydrated = finishedState.dehydrated;761          if (dehydrated !== null) {762            // Register a callback to retry this boundary once the server has sent the result.763            const retry = retryDehydratedSuspenseBoundary.bind(764              null,765              finishedWork,766            );767            registerSuspenseInstanceRetry(dehydrated, retry);768          }769        }770      }771      break;772    }773    case OffscreenComponent: {774      const isModernRoot =775        disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;776      if (isModernRoot) {777        const isHidden = finishedWork.memoizedState !== null;778        const newOffscreenSubtreeIsHidden =779          isHidden || offscreenSubtreeIsHidden;780        if (newOffscreenSubtreeIsHidden) {781          // The Offscreen tree is hidden. Skip over its layout effects.782        } else {783          // The Offscreen tree is visible.784785          const wasHidden = current !== null && current.memoizedState !== null;786          const newOffscreenSubtreeWasHidden =787            wasHidden || offscreenSubtreeWasHidden;788          const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;789          const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;790          offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;791          offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;792793          if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {794            // This is the root of a reappearing boundary. As we continue795            // traversing the layout effects, we must also re-mount layout796            // effects that were unmounted when the Offscreen subtree was797            // hidden. So this is a superset of the normal commitLayoutEffects.798            const includeWorkInProgressEffects =799              (finishedWork.subtreeFlags & LayoutMask) !== NoFlags;800            recursivelyTraverseReappearLayoutEffects(801              finishedRoot,802              finishedWork,803              includeWorkInProgressEffects,804            );805            if (806              enableProfilerTimer &&807              enableProfilerCommitHooks &&808              enableComponentPerformanceTrack &&809              (finishedWork.mode & ProfileMode) !== NoMode &&810              componentEffectStartTime >= 0 &&811              componentEffectEndTime >= 0 &&812              componentEffectEndTime - componentEffectStartTime > 0.05813            ) {814              logComponentReappeared(815                finishedWork,816                componentEffectStartTime,817                componentEffectEndTime,818              );819            }820          } else {821            recursivelyTraverseLayoutEffects(822              finishedRoot,823              finishedWork,824              committedLanes,825            );826          }827          offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;828          offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;829        }830      } else {831        recursivelyTraverseLayoutEffects(832          finishedRoot,833          finishedWork,834          committedLanes,835        );836      }837      break;838    }839    case ViewTransitionComponent: {840      if (enableViewTransition) {841        if (__DEV__) {842          if (flags & ViewTransitionNamedStatic) {843            trackNamedViewTransition(finishedWork);844          }845        }846        recursivelyTraverseLayoutEffects(847          finishedRoot,848          finishedWork,849          committedLanes,850        );851        if (flags & Ref) {852          safelyAttachRef(finishedWork, finishedWork.return);853        }854        break;855      }856      break;857    }858    case Fragment:859      if (enableFragmentRefs) {860        if (flags & Ref) {861          safelyAttachRef(finishedWork, finishedWork.return);862        }863      }864    // Fallthrough865    default: {866      recursivelyTraverseLayoutEffects(867        finishedRoot,868        finishedWork,869        committedLanes,870      );871      break;872    }873  }874875  if (876    enableProfilerTimer &&877    enableProfilerCommitHooks &&878    enableComponentPerformanceTrack &&879    (finishedWork.mode & ProfileMode) !== NoMode &&880    componentEffectStartTime >= 0 &&881    componentEffectEndTime >= 0882  ) {883    if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {884      logComponentEffect(885        finishedWork,886        componentEffectStartTime,887        componentEffectEndTime,888        componentEffectDuration,889        componentEffectErrors,890      );891    }892    if (893      // Insertion894      finishedWork.alternate === null &&895      finishedWork.return !== null &&896      finishedWork.return.alternate !== null &&897      componentEffectEndTime - componentEffectStartTime > 0.05898    ) {899      const isHydration = isHydratingParent(900        finishedWork.return.alternate,901        finishedWork.return,902      );903      if (!isHydration) {904        logComponentMount(905          finishedWork,906          componentEffectStartTime,907          componentEffectEndTime,908        );909      }910    }911  }912913  popComponentEffectStart(prevEffectStart);914  popComponentEffectDuration(prevEffectDuration);915  popComponentEffectErrors(prevEffectErrors);916  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);917}918919function abortRootTransitions(920  root: FiberRoot,921  abort: TransitionAbort,922  deletedTransitions: Set<Transition>,923  deletedOffscreenInstance: OffscreenInstance | null,924  isInDeletedTree: boolean,925) {926  if (enableTransitionTracing) {927    const rootTransitions = root.incompleteTransitions;928    deletedTransitions.forEach(transition => {929      if (rootTransitions.has(transition)) {930        const transitionInstance: TracingMarkerInstance = rootTransitions.get(931          transition,932        ) as any;933        if (transitionInstance.aborts === null) {934          transitionInstance.aborts = [];935        }936        transitionInstance.aborts.push(abort);937938        if (deletedOffscreenInstance !== null) {939          if (940            transitionInstance.pendingBoundaries !== null &&941            transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)942          ) {943            // $FlowFixMe[incompatible-use] found when upgrading Flow944            transitionInstance.pendingBoundaries.delete(945              deletedOffscreenInstance,946            );947          }948        }949      }950    });951  }952}953954function abortTracingMarkerTransitions(955  abortedFiber: Fiber,956  abort: TransitionAbort,957  deletedTransitions: Set<Transition>,958  deletedOffscreenInstance: OffscreenInstance | null,959  isInDeletedTree: boolean,960) {961  if (enableTransitionTracing) {962    const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;963    const markerTransitions = markerInstance.transitions;964    const pendingBoundaries = markerInstance.pendingBoundaries;965    if (markerTransitions !== null) {966      // TODO: Refactor this code. Is there a way to move this code to967      // the deletions phase instead of calculating it here while making sure968      // complete is called appropriately?969      deletedTransitions.forEach(transition => {970        // If one of the transitions on the tracing marker is a transition971        // that was in an aborted subtree, we will abort that tracing marker972        if (973          // $FlowFixMe[invalid-compare]974          abortedFiber !== null &&975          markerTransitions.has(transition) &&976          (markerInstance.aborts === null ||977            !markerInstance.aborts.includes(abort))978        ) {979          if (markerInstance.transitions !== null) {980            if (markerInstance.aborts === null) {981              markerInstance.aborts = [abort];982              addMarkerIncompleteCallbackToPendingTransition(983                abortedFiber.memoizedProps.name,984                markerInstance.transitions,985                markerInstance.aborts,986              );987            } else {988              markerInstance.aborts.push(abort);989            }990991            // We only want to call onTransitionProgress when the marker hasn't been992            // deleted993            if (994              deletedOffscreenInstance !== null &&995              !isInDeletedTree &&996              pendingBoundaries !== null &&997              pendingBoundaries.has(deletedOffscreenInstance)998            ) {999              pendingBoundaries.delete(deletedOffscreenInstance);10001001              addMarkerProgressCallbackToPendingTransition(1002                abortedFiber.memoizedProps.name,1003                deletedTransitions,1004                pendingBoundaries,1005              );1006            }1007          }1008        }1009      });1010    }1011  }1012}10131014function abortParentMarkerTransitionsForDeletedFiber(1015  abortedFiber: Fiber,1016  abort: TransitionAbort,1017  deletedTransitions: Set<Transition>,1018  deletedOffscreenInstance: OffscreenInstance | null,1019  isInDeletedTree: boolean,1020) {1021  if (enableTransitionTracing) {1022    // Find all pending markers that are waiting on child suspense boundaries in the1023    // aborted subtree and cancels them1024    let fiber: null | Fiber = abortedFiber;1025    while (fiber !== null) {1026      switch (fiber.tag) {1027        case TracingMarkerComponent:1028          abortTracingMarkerTransitions(1029            fiber,1030            abort,1031            deletedTransitions,1032            deletedOffscreenInstance,1033            isInDeletedTree,1034          );1035          break;1036        case HostRoot:1037          const root = fiber.stateNode;1038          abortRootTransitions(1039            root,1040            abort,1041            deletedTransitions,1042            deletedOffscreenInstance,1043            isInDeletedTree,1044          );10451046          break;1047        default:1048          break;1049      }10501051      fiber = fiber.return;1052    }1053  }1054}10551056function commitTransitionProgress(offscreenFiber: Fiber) {1057  if (enableTransitionTracing) {1058    // This function adds suspense boundaries to the root1059    // or tracing marker's pendingBoundaries map.1060    // When a suspense boundary goes from a resolved to a fallback1061    // state we add the boundary to the map, and when it goes from1062    // a fallback to a resolved state, we remove the boundary from1063    // the map.10641065    // We use stateNode on the Offscreen component as a stable object1066    // that doesnt change from render to render. This way we can1067    // distinguish between different Offscreen instances (vs. the same1068    // Offscreen instance with different fibers)1069    const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10701071    let prevState: SuspenseState | null = null;1072    const previousFiber = offscreenFiber.alternate;1073    if (previousFiber !== null && previousFiber.memoizedState !== null) {1074      prevState = previousFiber.memoizedState;1075    }1076    const nextState: SuspenseState | null = offscreenFiber.memoizedState;10771078    const wasHidden = prevState !== null;1079    const isHidden = nextState !== null;10801081    const pendingMarkers = offscreenInstance._pendingMarkers;1082    // If there is a name on the suspense boundary, store that in1083    // the pending boundaries.1084    let name = null;1085    const parent = offscreenFiber.return;1086    if (1087      parent !== null &&1088      parent.tag === SuspenseComponent &&1089      parent.memoizedProps.name1090    ) {1091      name = parent.memoizedProps.name;1092    }10931094    if (!wasHidden && isHidden) {1095      // The suspense boundaries was just hidden. Add the boundary1096      // to the pending boundary set if it's there1097      if (pendingMarkers !== null) {1098        pendingMarkers.forEach(markerInstance => {1099          const pendingBoundaries = markerInstance.pendingBoundaries;1100          const transitions = markerInstance.transitions;1101          const markerName = markerInstance.name;1102          if (1103            pendingBoundaries !== null &&1104            !pendingBoundaries.has(offscreenInstance)1105          ) {1106            pendingBoundaries.set(offscreenInstance, {1107              name,1108            });1109            if (transitions !== null) {1110              if (1111                markerInstance.tag === TransitionTracingMarker &&1112                markerName !== null1113              ) {1114                addMarkerProgressCallbackToPendingTransition(1115                  markerName,1116                  transitions,1117                  pendingBoundaries,1118                );1119              } else if (markerInstance.tag === TransitionRoot) {1120                transitions.forEach(transition => {1121                  addTransitionProgressCallbackToPendingTransition(1122                    transition,1123                    pendingBoundaries,1124                  );1125                });1126              }1127            }1128          }1129        });1130      }1131    } else if (wasHidden && !isHidden) {1132      // The suspense boundary went from hidden to visible. Remove1133      // the boundary from the pending suspense boundaries set1134      // if it's there1135      if (pendingMarkers !== null) {1136        pendingMarkers.forEach(markerInstance => {1137          const pendingBoundaries = markerInstance.pendingBoundaries;1138          const transitions = markerInstance.transitions;1139          const markerName = markerInstance.name;1140          if (1141            pendingBoundaries !== null &&1142            pendingBoundaries.has(offscreenInstance)1143          ) {1144            pendingBoundaries.delete(offscreenInstance);1145            if (transitions !== null) {1146              if (1147                markerInstance.tag === TransitionTracingMarker &&1148                markerName !== null1149              ) {1150                addMarkerProgressCallbackToPendingTransition(1151                  markerName,1152                  transitions,1153                  pendingBoundaries,1154                );11551156                // If there are no more unresolved suspense boundaries, the interaction1157                // is considered finished1158                if (pendingBoundaries.size === 0) {1159                  if (markerInstance.aborts === null) {1160                    addMarkerCompleteCallbackToPendingTransition(1161                      markerName,1162                      transitions,1163                    );1164                  }1165                  markerInstance.transitions = null;1166                  markerInstance.pendingBoundaries = null;1167                  markerInstance.aborts = null;1168                }1169              } else if (markerInstance.tag === TransitionRoot) {1170                transitions.forEach(transition => {1171                  addTransitionProgressCallbackToPendingTransition(1172                    transition,1173                    pendingBoundaries,1174                  );1175                });1176              }1177            }1178          }1179        });1180      }1181    }1182  }1183}11841185function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {1186  // $FlowFixMe[constant-condition]1187  if (!supportsMutation) {1188    return;1189  }1190  // Finds the nearest host component children and updates their visibility1191  // to either hidden or visible.1192  let child = parentFiber.child;1193  while (child !== null) {1194    hideOrUnhideAllChildrenOnFiber(child, isHidden);1195    child = child.sibling;1196  }1197}11981199function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {1200  // $FlowFixMe[constant-condition]1201  if (!supportsMutation) {1202    return;1203  }1204  switch (fiber.tag) {1205    case HostComponent:1206    case HostHoistable: {1207      // Found the nearest host component. Hide it.1208      commitShowHideHostInstance(fiber, isHidden);1209      // Typically, only the nearest host nodes need to be hidden, since that1210      // has the effect of also hiding everything inside of them.1211      //1212      // However, there's a special case for portals, because portals do not1213      // exist in the regular host tree hierarchy; we can't assume that just1214      // because a portal's HostComponent parent in the React tree will also be1215      // a parent in the actual host tree.1216      //1217      // So, if any portals exist within the tree, regardless of how deeply1218      // nested they are, we need to repeat this algorithm for its children.1219      hideOrUnhideNearestPortals(fiber, isHidden);1220      return;1221    }1222    case HostText: {1223      commitShowHideHostTextInstance(fiber, isHidden);1224      return;1225    }1226    case DehydratedFragment: {1227      commitShowHideSuspenseBoundary(fiber, isHidden);1228      return;1229    }1230    case OffscreenComponent:1231    case LegacyHiddenComponent: {1232      const offscreenState: OffscreenState | null = fiber.memoizedState;1233      if (offscreenState !== null) {1234        // Found a nested Offscreen component that is hidden.1235        // Don't search any deeper. This tree should remain hidden.1236      } else {1237        hideOrUnhideAllChildren(fiber, isHidden);1238      }1239      return;1240    }1241    default: {1242      hideOrUnhideAllChildren(fiber, isHidden);1243      return;1244    }1245  }1246}12471248function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {1249  // $FlowFixMe[constant-condition]1250  if (!supportsMutation) {1251    return;1252  }1253  if (parentFiber.subtreeFlags & PortalStatic) {1254    let child = parentFiber.child;1255    while (child !== null) {1256      hideOrUnhideNearestPortalsOnFiber(child, isHidden);1257      child = child.sibling;1258    }1259  }1260}12611262function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {1263  // $FlowFixMe[constant-condition]1264  if (!supportsMutation) {1265    return;1266  }1267  switch (fiber.tag) {1268    case HostPortal: {1269      // Found a portal. Switch back to the normal hide/unhide algorithm to1270      // toggle the visibility of its children.1271      hideOrUnhideAllChildrenOnFiber(fiber, isHidden);1272      return;1273    }1274    case OffscreenComponent: {1275      const offscreenState: OffscreenState | null = fiber.memoizedState;1276      if (offscreenState !== null) {1277        // Found a nested Offscreen component that is hidden. Don't search any1278        // deeper. This tree should remain hidden.1279      } else {1280        hideOrUnhideNearestPortals(fiber, isHidden);1281      }1282      return;1283    }1284    default: {1285      hideOrUnhideNearestPortals(fiber, isHidden);1286      return;1287    }1288  }1289}12901291function detachFiberMutation(fiber: Fiber) {1292  // Cut off the return pointer to disconnect it from the tree.1293  // This enables us to detect and warn against state updates on an unmounted component.1294  // It also prevents events from bubbling from within disconnected components.1295  //1296  // Ideally, we should also clear the child pointer of the parent alternate to let this1297  // get GC:ed but we don't know which for sure which parent is the current1298  // one so we'll settle for GC:ing the subtree of this child.1299  // This child itself will be GC:ed when the parent updates the next time.1300  //1301  // Note that we can't clear child or sibling pointers yet.1302  // They're needed for passive effects and for findDOMNode.1303  // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).1304  //1305  // Don't reset the alternate yet, either. We need that so we can detach the1306  // alternate's fields in the passive phase. Clearing the return pointer is1307  // sufficient for findDOMNode semantics.1308  const alternate = fiber.alternate;1309  if (alternate !== null) {1310    alternate.return = null;1311  }1312  fiber.return = null;1313}13141315function detachFiberAfterEffects(fiber: Fiber) {1316  const alternate = fiber.alternate;1317  if (alternate !== null) {1318    fiber.alternate = null;1319    detachFiberAfterEffects(alternate);1320  }13211322  // Clear cyclical Fiber fields. This level alone is designed to roughly1323  // approximate the planned Fiber refactor. In that world, `setState` will be1324  // bound to a special "instance" object instead of a Fiber. The Instance1325  // object will not have any of these fields. It will only be connected to1326  // the fiber tree via a single link at the root. So if this level alone is1327  // sufficient to fix memory issues, that bodes well for our plans.1328  fiber.child = null;1329  fiber.deletions = null;1330  fiber.sibling = null;13311332  // The `stateNode` is cyclical because on host nodes it points to the host1333  // tree, which has its own pointers to children, parents, and siblings.1334  // The other host nodes also point back to fibers, so we should detach that1335  // one, too.1336  if (fiber.tag === HostComponent) {1337    const hostInstance: Instance = fiber.stateNode;1338    // $FlowFixMe[invalid-compare]1339    if (hostInstance !== null) {1340      detachDeletedInstance(hostInstance);1341    }1342  }1343  fiber.stateNode = null;13441345  if (__DEV__) {1346    fiber._debugOwner = null;1347  }13481349  // Theoretically, nothing in here should be necessary, because we already1350  // disconnected the fiber from the tree. So even if something leaks this1351  // particular fiber, it won't leak anything else.1352  fiber.return = null;1353  fiber.dependencies = null;1354  fiber.memoizedProps = null;1355  fiber.memoizedState = null;1356  fiber.pendingProps = null;1357  fiber.stateNode = null;1358  // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.1359  fiber.updateQueue = null;1360}13611362// These are tracked on the stack as we recursively traverse a1363// deleted subtree.1364// TODO: Update these during the whole mutation phase, not just during1365// a deletion.1366let hostParent: Instance | Container | null = null;1367let hostParentIsContainer: boolean = false;13681369function commitDeletionEffects(1370  root: FiberRoot,1371  returnFiber: Fiber,1372  deletedFiber: Fiber,1373) {1374  const prevEffectStart = pushComponentEffectStart();13751376  // $FlowFixMe[constant-condition]1377  if (supportsMutation) {1378    // We only have the top Fiber that was deleted but we need to recurse down its1379    // children to find all the terminal nodes.13801381    // Recursively delete all host nodes from the parent, detach refs, clean1382    // up mounted layout effects, and call componentWillUnmount.13831384    // We only need to remove the topmost host child in each branch. But then we1385    // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1386    // could split this into two separate traversals functions, where the second1387    // one doesn't include any removeChild logic. This is maybe the same1388    // function as "disappearLayoutEffects" (or whatever that turns into after1389    // the layout phase is refactored to use recursion).13901391    // Before starting, find the nearest host parent on the stack so we know1392    // which instance/container to remove the children from.1393    // TODO: Instead of searching up the fiber return path on every deletion, we1394    // can track the nearest host component on the JS stack as we traverse the1395    // tree during the commit phase. This would make insertions faster, too.1396    let parent: null | Fiber = returnFiber;1397    findParent: while (parent !== null) {1398      switch (parent.tag) {1399        case HostSingleton: {1400          // $FlowFixMe[constant-condition]1401          if (supportsSingletons) {1402            if (isSingletonScope(parent.type)) {1403              hostParent = parent.stateNode;1404              hostParentIsContainer = false;1405              break findParent;1406            }1407            break;1408          }1409          // Expected fallthrough when supportsSingletons is false1410        }1411        case HostComponent: {1412          hostParent = parent.stateNode;1413          hostParentIsContainer = false;1414          break findParent;1415        }1416        case HostRoot:1417        case HostPortal: {1418          hostParent = parent.stateNode.containerInfo;1419          hostParentIsContainer = true;1420          break findParent;1421        }1422      }1423      parent = parent.return;1424    }1425    if (hostParent === null) {1426      throw new Error(1427        'Expected to find a host parent. This error is likely caused by ' +1428          'a bug in React. Please file an issue.',1429      );1430    }14311432    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1433    hostParent = null;1434    hostParentIsContainer = false;1435  } else {1436    // Detach refs and call componentWillUnmount() on the whole subtree.1437    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1438  }14391440  if (1441    enableProfilerTimer &&1442    enableProfilerCommitHooks &&1443    enableComponentPerformanceTrack &&1444    (deletedFiber.mode & ProfileMode) !== NoMode &&1445    componentEffectStartTime >= 0 &&1446    componentEffectEndTime >= 0 &&1447    componentEffectEndTime - componentEffectStartTime > 0.051448  ) {1449    logComponentUnmount(1450      deletedFiber,1451      componentEffectStartTime,1452      componentEffectEndTime,1453    );1454  }1455  popComponentEffectStart(prevEffectStart);14561457  detachFiberMutation(deletedFiber);1458}14591460function recursivelyTraverseDeletionEffects(1461  finishedRoot: FiberRoot,1462  nearestMountedAncestor: Fiber,1463  parent: Fiber,1464) {1465  // TODO: Use a static flag to skip trees that don't have unmount effects1466  let child = parent.child;1467  while (child !== null) {1468    commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1469    child = child.sibling;1470  }1471}14721473function commitDeletionEffectsOnFiber(1474  finishedRoot: FiberRoot,1475  nearestMountedAncestor: Fiber,1476  deletedFiber: Fiber,1477) {1478  // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.1479  onCommitUnmount(deletedFiber);14801481  const prevEffectStart = pushComponentEffectStart();1482  const prevEffectDuration = pushComponentEffectDuration();1483  const prevEffectErrors = pushComponentEffectErrors();1484  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();14851486  // The cases in this outer switch modify the stack before they traverse1487  // into their subtree. There are simpler cases in the inner switch1488  // that don't modify the stack.1489  switch (deletedFiber.tag) {1490    case HostHoistable: {1491      // $FlowFixMe[constant-condition]1492      if (supportsResources) {1493        if (!offscreenSubtreeWasHidden) {1494          safelyDetachRef(deletedFiber, nearestMountedAncestor);1495        }1496        recursivelyTraverseDeletionEffects(1497          finishedRoot,1498          nearestMountedAncestor,1499          deletedFiber,1500        );1501        if (deletedFiber.memoizedState) {1502          releaseResource(deletedFiber.memoizedState);1503        } else if (deletedFiber.stateNode) {1504          unmountHoistable(deletedFiber.stateNode);1505        }1506        break;1507      }1508      // Fall through1509    }1510    case HostSingleton: {1511      // $FlowFixMe[constant-condition]1512      if (supportsSingletons) {1513        if (!offscreenSubtreeWasHidden) {1514          safelyDetachRef(deletedFiber, nearestMountedAncestor);1515        }15161517        const prevHostParent = hostParent;1518        const prevHostParentIsContainer = hostParentIsContainer;1519        if (isSingletonScope(deletedFiber.type)) {1520          hostParent = deletedFiber.stateNode;1521          hostParentIsContainer = false;1522        }1523        recursivelyTraverseDeletionEffects(1524          finishedRoot,1525          nearestMountedAncestor,1526          deletedFiber,1527        );15281529        // Normally this is called in passive unmount effect phase however with1530        // HostSingleton we warn if you acquire one that is already associated to1531        // a different fiber. To increase our chances of avoiding this, specifically1532        // if you keyed a HostSingleton so there will be a delete followed by a Placement1533        // we treat detach eagerly here1534        commitHostSingletonRelease(deletedFiber);15351536        hostParent = prevHostParent;1537        hostParentIsContainer = prevHostParentIsContainer;15381539        break;1540      }1541      // Fall through1542    }1543    case HostComponent: {1544      if (!offscreenSubtreeWasHidden) {1545        safelyDetachRef(deletedFiber, nearestMountedAncestor);1546      }1547      if (1548        enableFragmentRefs &&1549        (deletedFiber.tag === HostComponent ||1550          (enableFragmentRefsTextNodes && deletedFiber.tag === HostText))1551      ) {1552        commitFragmentInstanceDeletionEffects(deletedFiber);1553      }1554      // Intentional fallthrough to next branch1555    }1556    case HostText: {1557      // We only need to remove the nearest host child. Set the host parent1558      // to `null` on the stack to indicate that nested children don't1559      // need to be removed.1560      // $FlowFixMe[constant-condition]1561      if (supportsMutation) {1562        const prevHostParent = hostParent;1563        const prevHostParentIsContainer = hostParentIsContainer;1564        hostParent = null;1565        recursivelyTraverseDeletionEffects(1566          finishedRoot,1567          nearestMountedAncestor,1568          deletedFiber,1569        );1570        hostParent = prevHostParent;1571        hostParentIsContainer = prevHostParentIsContainer;15721573        if (hostParent !== null) {1574          // Now that all the child effects have unmounted, we can remove the1575          // node from the tree.1576          if (hostParentIsContainer) {1577            commitHostRemoveChildFromContainer(1578              deletedFiber,1579              nearestMountedAncestor,1580              hostParent as any as Container,1581              deletedFiber.stateNode as Instance | TextInstance,1582            );1583          } else {1584            commitHostRemoveChild(1585              deletedFiber,1586              nearestMountedAncestor,1587              hostParent as any as Instance,1588              deletedFiber.stateNode as Instance | TextInstance,1589            );1590          }1591        }1592      } else {1593        recursivelyTraverseDeletionEffects(1594          finishedRoot,1595          nearestMountedAncestor,1596          deletedFiber,1597        );1598      }1599      break;1600    }1601    case DehydratedFragment: {1602      if (enableSuspenseCallback) {1603        const hydrationCallbacks = finishedRoot.hydrationCallbacks;1604        if (hydrationCallbacks !== null) {1605          try {1606            const onDeleted = hydrationCallbacks.onDeleted;1607            if (onDeleted) {1608              onDeleted(1609                deletedFiber.stateNode as SuspenseInstance | ActivityInstance,1610              );1611            }1612          } catch (error) {1613            captureCommitPhaseError(1614              deletedFiber,1615              nearestMountedAncestor,1616              error,1617            );1618          }1619        }1620      }16211622      // Dehydrated fragments don't have any children16231624      // Delete the dehydrated suspense boundary and all of its content.1625      // $FlowFixMe[constant-condition]1626      if (supportsMutation) {1627        if (hostParent !== null) {1628          if (hostParentIsContainer) {1629            clearSuspenseBoundaryFromContainer(1630              hostParent as any as Container,1631              deletedFiber.stateNode as SuspenseInstance,1632            );1633          } else {1634            clearSuspenseBoundary(1635              hostParent as any as Instance,1636              deletedFiber.stateNode as SuspenseInstance,1637            );1638          }1639        }1640      }1641      break;1642    }1643    case HostPortal: {1644      // $FlowFixMe[constant-condition]1645      if (supportsMutation) {1646        // When we go into a portal, it becomes the parent to remove from.1647        const prevHostParent = hostParent;1648        const prevHostParentIsContainer = hostParentIsContainer;1649        hostParent = deletedFiber.stateNode.containerInfo;1650        hostParentIsContainer = true;1651        recursivelyTraverseDeletionEffects(1652          finishedRoot,1653          nearestMountedAncestor,1654          deletedFiber,1655        );1656        hostParent = prevHostParent;1657        hostParentIsContainer = prevHostParentIsContainer;1658      } else {1659        // $FlowFixMe[constant-condition]1660        if (supportsPersistence) {1661          commitHostPortalContainerChildren(1662            deletedFiber.stateNode,1663            deletedFiber,1664            createContainerChildSet(),1665          );1666        }16671668        recursivelyTraverseDeletionEffects(1669          finishedRoot,1670          nearestMountedAncestor,1671          deletedFiber,1672        );1673      }1674      break;1675    }1676    case FunctionComponent:1677    case ForwardRef:1678    case MemoComponent:1679    case SimpleMemoComponent: {1680      // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.1681      commitHookEffectListUnmount(1682        HookInsertion,1683        deletedFiber,1684        nearestMountedAncestor,1685      );1686      if (!offscreenSubtreeWasHidden) {1687        commitHookLayoutUnmountEffects(1688          deletedFiber,1689          nearestMountedAncestor,1690          HookLayout,1691        );1692      }1693      recursivelyTraverseDeletionEffects(1694        finishedRoot,1695        nearestMountedAncestor,1696        deletedFiber,1697      );1698      break;1699    }1700    case ClassComponent: {1701      if (!offscreenSubtreeWasHidden) {1702        safelyDetachRef(deletedFiber, nearestMountedAncestor);1703        const instance = deletedFiber.stateNode;1704        if (typeof instance.componentWillUnmount === 'function') {1705          safelyCallComponentWillUnmount(1706            deletedFiber,1707            nearestMountedAncestor,1708            instance,1709          );1710        }1711      }1712      recursivelyTraverseDeletionEffects(1713        finishedRoot,1714        nearestMountedAncestor,1715        deletedFiber,1716      );1717      break;1718    }1719    case ScopeComponent: {1720      if (enableScopeAPI) {1721        if (!offscreenSubtreeWasHidden) {1722          safelyDetachRef(deletedFiber, nearestMountedAncestor);1723        }1724      }1725      recursivelyTraverseDeletionEffects(1726        finishedRoot,1727        nearestMountedAncestor,1728        deletedFiber,1729      );1730      break;1731    }1732    case OffscreenComponent: {1733      if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {1734        // If this offscreen component is hidden, we already unmounted it. Before1735        // deleting the children, track that it's already unmounted so that we1736        // don't attempt to unmount the effects again.1737        // TODO: If the tree is hidden, in most cases we should be able to skip1738        // over the nested children entirely. An exception is we haven't yet found1739        // the topmost host node to delete, which we already track on the stack.1740        // But the other case is portals, which need to be detached no matter how1741        // deeply they are nested. We should use a subtree flag to track whether a1742        // subtree includes a nested portal.1743        const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1744        offscreenSubtreeWasHidden =1745          prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17461747        recursivelyTraverseDeletionEffects(1748          finishedRoot,1749          nearestMountedAncestor,1750          deletedFiber,1751        );1752        offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;1753      } else {1754        recursivelyTraverseDeletionEffects(1755          finishedRoot,1756          nearestMountedAncestor,1757          deletedFiber,1758        );1759      }1760      break;1761    }1762    case ViewTransitionComponent: {1763      if (enableViewTransition) {1764        if (__DEV__) {1765          if (deletedFiber.flags & ViewTransitionNamedStatic) {1766            untrackNamedViewTransition(deletedFiber);1767          }1768        }1769        safelyDetachRef(deletedFiber, nearestMountedAncestor);1770        recursivelyTraverseDeletionEffects(1771          finishedRoot,1772          nearestMountedAncestor,1773          deletedFiber,1774        );1775        break;1776      }1777      // Fallthrough1778    }1779    case Fragment: {1780      if (enableFragmentRefs) {1781        if (!offscreenSubtreeWasHidden) {1782          safelyDetachRef(deletedFiber, nearestMountedAncestor);1783        }1784        recursivelyTraverseDeletionEffects(1785          finishedRoot,1786          nearestMountedAncestor,1787          deletedFiber,1788        );1789        break;1790      }1791      // Fallthrough1792    }1793    default: {1794      recursivelyTraverseDeletionEffects(1795        finishedRoot,1796        nearestMountedAncestor,1797        deletedFiber,1798      );1799      break;1800    }1801  }18021803  if (1804    enableProfilerTimer &&1805    enableProfilerCommitHooks &&1806    enableComponentPerformanceTrack &&1807    (deletedFiber.mode & ProfileMode) !== NoMode &&1808    componentEffectStartTime >= 0 &&1809    componentEffectEndTime >= 0 &&1810    (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)1811  ) {1812    logComponentEffect(1813      deletedFiber,1814      componentEffectStartTime,1815      componentEffectEndTime,1816      componentEffectDuration,1817      componentEffectErrors,1818    );1819  }18201821  popComponentEffectStart(prevEffectStart);1822  popComponentEffectDuration(prevEffectDuration);1823  popComponentEffectErrors(prevEffectErrors);1824  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1825}18261827function commitSuspenseCallback(finishedWork: Fiber) {1828  // TODO: Delete this feature. It's not properly covered by DEV features.1829  const newState: SuspenseState | null = finishedWork.memoizedState;1830  if (enableSuspenseCallback && newState !== null) {1831    const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;1832    if (typeof suspenseCallback === 'function') {1833      const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;1834      if (retryQueue !== null) {1835        suspenseCallback(new Set(retryQueue));1836      }1837    } else if (__DEV__) {1838      if (suspenseCallback !== undefined) {1839        console.error('Unexpected type for suspenseCallback.');1840      }1841    }1842  }1843}18441845function commitActivityHydrationCallbacks(1846  finishedRoot: FiberRoot,1847  finishedWork: Fiber,1848) {1849  // $FlowFixMe[constant-condition]1850  if (!supportsHydration) {1851    return;1852  }1853  const newState: ActivityState | null = finishedWork.memoizedState;1854  if (newState === null) {1855    const current = finishedWork.alternate;1856    if (current !== null) {1857      const prevState: ActivityState | null = current.memoizedState;1858      if (prevState !== null) {1859        const activityInstance = prevState.dehydrated;1860        commitHostHydratedActivity(activityInstance, finishedWork);1861        if (enableSuspenseCallback) {1862          try {1863            // TODO: Delete this feature. It's not properly covered by DEV features.1864            const hydrationCallbacks = finishedRoot.hydrationCallbacks;1865            if (hydrationCallbacks !== null) {1866              const onHydrated = hydrationCallbacks.onHydrated;1867              if (onHydrated) {1868                onHydrated(activityInstance);1869              }1870            }1871          } catch (error) {1872            captureCommitPhaseError(finishedWork, finishedWork.return, error);1873          }1874        }1875      }1876    }1877  }1878}18791880function commitSuspenseHydrationCallbacks(1881  finishedRoot: FiberRoot,1882  finishedWork: Fiber,1883) {1884  // $FlowFixMe[constant-condition]1885  if (!supportsHydration) {1886    return;1887  }1888  const newState: SuspenseState | null = finishedWork.memoizedState;1889  if (newState === null) {1890    const current = finishedWork.alternate;1891    if (current !== null) {1892      const prevState: SuspenseState | null = current.memoizedState;1893      if (prevState !== null) {1894        const suspenseInstance = prevState.dehydrated;1895        if (suspenseInstance !== null) {1896          commitHostHydratedSuspense(suspenseInstance, finishedWork);1897          if (enableSuspenseCallback) {1898            try {1899              // TODO: Delete this feature. It's not properly covered by DEV features.1900              const hydrationCallbacks = finishedRoot.hydrationCallbacks;1901              if (hydrationCallbacks !== null) {1902                const onHydrated = hydrationCallbacks.onHydrated;1903                if (onHydrated) {1904                  onHydrated(suspenseInstance);1905                }1906              }1907            } catch (error) {1908              captureCommitPhaseError(finishedWork, finishedWork.return, error);1909            }1910          }1911        }1912      }1913    }1914  }1915}19161917function getRetryCache(finishedWork: Fiber) {1918  // TODO: Unify the interface for the retry cache so we don't have to switch1919  // on the tag like this.1920  switch (finishedWork.tag) {1921    case ActivityComponent:1922    case SuspenseComponent:1923    case SuspenseListComponent: {1924      let retryCache = finishedWork.stateNode;1925      if (retryCache === null) {1926        retryCache = finishedWork.stateNode = new PossiblyWeakSet();1927      }1928      return retryCache;1929    }1930    case OffscreenComponent: {1931      const instance: OffscreenInstance = finishedWork.stateNode;1932      let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =1933        instance._retryCache;1934      if (retryCache === null) {1935        retryCache = instance._retryCache = new PossiblyWeakSet();1936      }1937      return retryCache;1938    }1939    default: {1940      throw new Error(1941        `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +1942          'bug in React.',1943      );1944    }1945  }1946}19471948function attachSuspenseRetryListeners(1949  finishedWork: Fiber,1950  wakeables: RetryQueue,1951) {1952  // If this boundary just timed out, then it will have a set of wakeables.1953  // For each wakeable, attach a listener so that when it resolves, React1954  // attempts to re-render the boundary in the primary (pre-timeout) state.1955  const retryCache = getRetryCache(finishedWork);1956  wakeables.forEach(wakeable => {1957    // Memoize using the boundary fiber to prevent redundant listeners.1958    if (!retryCache.has(wakeable)) {1959      retryCache.add(wakeable);19601961      if (enableUpdaterTracking) {1962        if (isDevToolsPresent) {1963          if (inProgressLanes !== null && inProgressRoot !== null) {1964            // If we have pending work still, associate the original updaters with it.1965            restorePendingUpdaters(inProgressRoot, inProgressLanes);1966          } else {1967            throw Error(1968              'Expected finished root and lanes to be set. This is a bug in React.',1969            );1970          }1971        }1972      }19731974      const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);1975      wakeable.then(retry, retry);1976    }1977  });1978}19791980// This function detects when a Suspense boundary goes from visible to hidden.1981// It returns false if the boundary is already hidden.1982// TODO: Use an effect tag.1983function isSuspenseBoundaryBeingHidden(1984  current: Fiber | null,1985  finishedWork: Fiber,1986): boolean {1987  if (current !== null) {1988    const oldState: SuspenseState | null = current.memoizedState;1989    if (oldState === null || oldState.dehydrated !== null) {1990      const newState: SuspenseState | null = finishedWork.memoizedState;1991      return newState !== null && newState.dehydrated === null;1992    }1993  }1994  return false;1995}19961997export function commitMutationEffects(1998  root: FiberRoot,1999  finishedWork: Fiber,2000  committedLanes: Lanes,

Findings

✓ No findings reported for this file.

Get this view in your editor

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