packages/react-reconciler/src/ReactFiberCommitWork.js JAVASCRIPT 5,540 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,540.
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  enableProfilerTimer,51  enableProfilerCommitHooks,52  enableSuspenseCallback,53  enableScopeAPI,54  enableUpdaterTracking,55  enableTransitionTracing,56  enableLegacyHidden,57  disableLegacyMode,58  enableComponentPerformanceTrack,59  enableViewTransition,60  enableDefaultTransitionIndicator,61} from 'shared/ReactFeatureFlags';62import {63  FunctionComponent,64  ForwardRef,65  ClassComponent,66  HostRoot,67  HostComponent,68  HostHoistable,69  HostSingleton,70  HostText,71  HostPortal,72  Profiler,73  ActivityComponent,74  SuspenseComponent,75  DehydratedFragment,76  IncompleteClassComponent,77  MemoComponent,78  SimpleMemoComponent,79  SuspenseListComponent,80  ScopeComponent,81  OffscreenComponent,82  LegacyHiddenComponent,83  CacheComponent,84  TracingMarkerComponent,85  ViewTransitionComponent,86  Fragment,87} from './ReactWorkTags';88import {89  NoFlags,90  ContentReset,91  Placement,92  ChildDeletion,93  Snapshot,94  Update,95  Hydrate,96  Callback,97  Ref,98  Hydrating,99  Passive,100  BeforeMutationMask,101  BeforeAndAfterMutationTransitionMask,102  MutationMask,103  LayoutMask,104  PassiveMask,105  PassiveTransitionMask,106  Visibility,107  ShouldSuspendCommit,108  MaySuspendCommit,109  FormReset,110  Cloned,111  PerformedWork,112  ForceClientRender,113  DidCapture,114  AffectedParentLayout,115  ViewTransitionNamedStatic,116  PortalStatic,117} from './ReactFiberFlags';118import {119  commitStartTime,120  pushNestedEffectDurations,121  popNestedEffectDurations,122  bubbleNestedEffectDurations,123  resetComponentEffectTimers,124  pushComponentEffectStart,125  popComponentEffectStart,126  pushComponentEffectDuration,127  popComponentEffectDuration,128  pushComponentEffectErrors,129  popComponentEffectErrors,130  pushComponentEffectDidSpawnUpdate,131  popComponentEffectDidSpawnUpdate,132  componentEffectStartTime,133  componentEffectEndTime,134  componentEffectDuration,135  componentEffectErrors,136  componentEffectSpawnedUpdate,137} from './ReactProfilerTimer';138import {139  logComponentRender,140  logComponentErrored,141  logComponentEffect,142  logComponentMount,143  logComponentUnmount,144  logComponentReappeared,145  logComponentDisappeared,146  pushDeepEquality,147  popDeepEquality,148} from './ReactFiberPerformanceTrack';149import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';150import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';151import {152  supportsMutation,153  supportsPersistence,154  supportsHydration,155  supportsResources,156  supportsSingletons,157  clearSuspenseBoundary,158  clearSuspenseBoundaryFromContainer,159  createContainerChildSet,160  clearContainer,161  prepareScopeUpdate,162  prepareForCommit,163  beforeActiveInstanceBlur,164  detachDeletedInstance,165  getHoistableRoot,166  acquireResource,167  releaseResource,168  hydrateHoistable,169  createHoistableInstance,170  mountHoistable,171  unmountHoistable,172  prepareToCommitHoistables,173  maySuspendCommitInSyncRender,174  suspendInstance,175  suspendResource,176  resetFormInstance,177  registerSuspenseInstanceRetry,178  cancelViewTransitionName,179  cancelRootViewTransitionName,180  restoreRootViewTransitionName,181  isSingletonScope,182  updateFragmentInstanceFiber,183} from './ReactFiberConfig';184import {185  captureCommitPhaseError,186  resolveRetryWakeable,187  markCommitTimeOfFallback,188  restorePendingUpdaters,189  addTransitionStartCallbackToPendingTransition,190  addTransitionProgressCallbackToPendingTransition,191  addTransitionCompleteCallbackToPendingTransition,192  addMarkerProgressCallbackToPendingTransition,193  addMarkerIncompleteCallbackToPendingTransition,194  addMarkerCompleteCallbackToPendingTransition,195  retryDehydratedSuspenseBoundary,196  scheduleViewTransitionEvent,197} from './ReactFiberWorkLoop';198import {199  HasEffect as HookHasEffect,200  Layout as HookLayout,201  Insertion as HookInsertion,202  Passive as HookPassive,203} from './ReactHookEffectTags';204import {doesFiberContain} from './ReactFiberTreeReflection';205import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';206import {releaseCache, retainCache} from './ReactFiberCacheComponent';207import {clearTransitionsForLanes} from './ReactFiberLane';208import {209  OffscreenVisible,210  OffscreenPassiveEffectsConnected,211} from './ReactFiberOffscreenComponent';212import {213  TransitionRoot,214  TransitionTracingMarker,215} from './ReactFiberTracingMarkerComponent';216import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';217import {218  commitHookLayoutEffects,219  commitHookLayoutUnmountEffects,220  commitHookEffectListMount,221  commitHookEffectListUnmount,222  commitHookPassiveMountEffects,223  commitHookPassiveUnmountEffects,224  commitClassLayoutLifecycles,225  commitClassDidMount,226  commitClassCallbacks,227  commitClassHiddenCallbacks,228  commitClassSnapshot,229  safelyCallComponentWillUnmount,230  safelyAttachRef,231  safelyDetachRef,232  commitProfilerUpdate,233  commitProfilerPostCommit,234  commitRootCallbacks,235} from './ReactFiberCommitEffects';236import {237  commitHostMount,238  commitHostHydratedInstance,239  commitHostUpdate,240  commitHostTextUpdate,241  commitHostResetTextContent,242  commitShowHideSuspenseBoundary,243  commitShowHideHostInstance,244  commitShowHideHostTextInstance,245  commitHostPlacement,246  commitHostRootContainerChildren,247  commitHostPortalContainerChildren,248  commitHostHydratedContainer,249  commitHostHydratedActivity,250  commitHostHydratedSuspense,251  commitHostRemoveChildFromContainer,252  commitHostRemoveChild,253  commitHostSingletonAcquisition,254  commitHostSingletonRelease,255} from './ReactFiberCommitHostEffects';256import {257  commitFragmentInstanceDeletionEffects,258  commitFragmentInstanceInsertionEffects,259} from './ReactFiberFragmentInstance';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';290291type LayoutEffectTraversalFlags = number;292293const NoLayoutEffectTraversalFlags = /*        */ 0b00;294const IncludeWorkInProgressEffects = /*       */ 0b01;295const IncludeHostSingletons = /*              */ 0b10;296297// Used during the commit phase to track the state of the Offscreen component stack.298// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.299let offscreenSubtreeIsHidden: boolean = false;300let offscreenSubtreeWasHidden: boolean = false;301// Track whether there's a hidden offscreen above with no HostComponent between. If so,302// it overrides the hiddenness of the HostComponent below.303let offscreenDirectParentIsHidden: boolean = false;304305// Used to track if a form needs to be reset at the end of the mutation phase.306let needsFormReset = false;307308const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;309310let nextEffect: Fiber | null = null;311312// Used for Profiling builds to track updaters.313let inProgressLanes: Lanes | null = null;314let inProgressRoot: FiberRoot | null = null;315316let focusedInstanceHandle: null | Fiber = null;317export let shouldFireAfterActiveInstanceBlur: boolean = false;318319// Used during the commit phase to track whether a parent ViewTransition component320// might have been affected by any mutations / relayouts below.321let viewTransitionContextChanged: boolean = false;322let inUpdateViewTransition: boolean = false;323let rootViewTransitionAffected: boolean = false;324let rootViewTransitionNameCanceled: boolean = false;325326function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {327  if (finishedWork.tag === ActivityComponent) {328    const prevState: ActivityState | null = current.memoizedState;329    const nextState: ActivityState | null = finishedWork.memoizedState;330    return prevState !== null && nextState === null;331  } else if (finishedWork.tag === SuspenseComponent) {332    const prevState: SuspenseState | null = current.memoizedState;333    const nextState: SuspenseState | null = finishedWork.memoizedState;334    return (335      prevState !== null &&336      prevState.dehydrated !== null &&337      (nextState === null || nextState.dehydrated === null)338    );339  } else if (finishedWork.tag === HostRoot) {340    return (341      (current.memoizedState as RootState).isDehydrated &&342      (finishedWork.flags & ForceClientRender) === NoFlags343    );344  } else {345    return false;346  }347}348349export function commitBeforeMutationEffects(350  root: FiberRoot,351  firstChild: Fiber,352  committedLanes: Lanes,353): void {354  focusedInstanceHandle = prepareForCommit(root.containerInfo);355  shouldFireAfterActiveInstanceBlur = false;356357  const isViewTransitionEligible =358    enableViewTransition &&359    includesOnlyViewTransitionEligibleLanes(committedLanes);360361  nextEffect = firstChild;362  commitBeforeMutationEffects_begin(isViewTransitionEligible);363364  // We no longer need to track the active instance fiber365  focusedInstanceHandle = null;366  // We've found any matched pairs and can now reset.367  resetAppearingViewTransitions();368}369370function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {371  // If this commit is eligible for a View Transition we look into all mutated subtrees.372  // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.373  const subtreeMask = isViewTransitionEligible374    ? BeforeAndAfterMutationTransitionMask375    : BeforeMutationMask;376  while (nextEffect !== null) {377    const fiber = nextEffect;378379    // This phase is only used for beforeActiveInstanceBlur.380    // Let's skip the whole loop if it's off.381    if (enableCreateEventHandleAPI || isViewTransitionEligible) {382      // TODO: Should wrap this in flags check, too, as optimization383      const deletions = fiber.deletions;384      if (deletions !== null) {385        for (let i = 0; i < deletions.length; i++) {386          const deletion = deletions[i];387          commitBeforeMutationEffectsDeletion(388            deletion,389            isViewTransitionEligible,390          );391        }392      }393    }394395    if (396      enableViewTransition &&397      fiber.alternate === null &&398      (fiber.flags & Placement) !== NoFlags399    ) {400      // Skip before mutation effects of the children because we don't want401      // to trigger updates of any nested view transitions and we shouldn't402      // have any other before mutation effects since snapshot effects are403      // only applied to updates. TODO: Model this using only flags.404      if (isViewTransitionEligible) {405        trackEnterViewTransitions(fiber);406      }407      commitBeforeMutationEffects_complete(isViewTransitionEligible);408      continue;409    }410411    // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.412    if (enableViewTransition && fiber.tag === OffscreenComponent) {413      const isModernRoot =414        disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;415      if (isModernRoot) {416        const current = fiber.alternate;417        const isHidden = fiber.memoizedState !== null;418        if (isHidden) {419          if (420            current !== null &&421            current.memoizedState === null &&422            isViewTransitionEligible423          ) {424            // Was previously mounted as visible but is now hidden.425            commitExitViewTransitions(current);426          }427          // Skip before mutation effects of the children because they're hidden.428          commitBeforeMutationEffects_complete(isViewTransitionEligible);429          continue;430        } else if (current !== null && current.memoizedState !== null) {431          // Was previously mounted as hidden but is now visible.432          // Skip before mutation effects of the children because we don't want433          // to trigger updates of any nested view transitions and we shouldn't434          // have any other before mutation effects since snapshot effects are435          // only applied to updates. TODO: Model this using only flags.436          if (isViewTransitionEligible) {437            trackEnterViewTransitions(fiber);438          }439          commitBeforeMutationEffects_complete(isViewTransitionEligible);440          continue;441        }442      }443    }444445    const child = fiber.child;446    if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {447      child.return = fiber;448      nextEffect = child;449    } else {450      if (isViewTransitionEligible) {451        // We are inside an updated subtree. Any mutations that affected the452        // parent HostInstance's layout or set of children (such as reorders)453        // might have also affected the positioning or size of the inner454        // ViewTransitions. Therefore we need to find them inside.455        commitNestedViewTransitions(fiber);456      }457      commitBeforeMutationEffects_complete(isViewTransitionEligible);458    }459  }460}461462function commitBeforeMutationEffects_complete(463  isViewTransitionEligible: boolean,464) {465  while (nextEffect !== null) {466    const fiber = nextEffect;467    commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);468469    const sibling = fiber.sibling;470    if (sibling !== null) {471      sibling.return = fiber.return;472      nextEffect = sibling;473      return;474    }475476    nextEffect = fiber.return;477  }478}479480function commitBeforeMutationEffectsOnFiber(481  finishedWork: Fiber,482  isViewTransitionEligible: boolean,483) {484  const current = finishedWork.alternate;485  const flags = finishedWork.flags;486487  if (enableCreateEventHandleAPI) {488    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {489      // Check to see if the focused element was inside of a hidden (Suspense) subtree.490      // TODO: Move this out of the hot path using a dedicated effect tag.491      // TODO: This should consider Offscreen in general and not just SuspenseComponent.492      if (493        finishedWork.tag === SuspenseComponent &&494        isSuspenseBoundaryBeingHidden(current, finishedWork) &&495        // $FlowFixMe[incompatible-type] found when upgrading Flow496        doesFiberContain(finishedWork, focusedInstanceHandle)497      ) {498        shouldFireAfterActiveInstanceBlur = true;499        beforeActiveInstanceBlur(finishedWork);500      }501    }502  }503504  switch (finishedWork.tag) {505    case ClassComponent: {506      if ((flags & Snapshot) !== NoFlags) {507        if (current !== null) {508          commitClassSnapshot(finishedWork, current);509        }510      }511      break;512    }513    case HostRoot: {514      if ((flags & Snapshot) !== NoFlags) {515        if (supportsMutation) {516          const root = finishedWork.stateNode;517          clearContainer(root.containerInfo);518        }519      }520      break;521    }522    case FunctionComponent:523    case ForwardRef:524    case SimpleMemoComponent:525    case HostComponent:526    case HostHoistable:527    case HostSingleton:528    case HostText:529    case HostPortal:530    case IncompleteClassComponent:531      // Nothing to do for these component types532      break;533    case ViewTransitionComponent:534      if (enableViewTransition) {535        if (isViewTransitionEligible) {536          if (current === null) {537            // This is a new mount. We should have handled this as part of the538            // Placement effect or it is deeper inside a entering transition.539          } else {540            // Something may have mutated within this subtree. This might need to cause541            // a cross-fade of this parent. We first assign old names to the542            // previous tree in the before mutation phase in case we need to.543            // TODO: This walks the tree that we might continue walking anyway.544            // We should just stash the parent ViewTransitionComponent and continue545            // walking the tree until we find HostComponent but to do that we need546            // to use a stack which requires refactoring this phase.547            commitBeforeUpdateViewTransition(current, finishedWork);548          }549        }550        break;551      }552    // Fallthrough553    default: {554      if ((flags & Snapshot) !== NoFlags) {555        throw new Error(556          'This unit of work tag should not have side-effects. This error is ' +557            'likely caused by a bug in React. Please file an issue.',558        );559      }560    }561  }562}563564function commitBeforeMutationEffectsDeletion(565  deletion: Fiber,566  isViewTransitionEligible: boolean,567) {568  if (enableCreateEventHandleAPI) {569    // TODO (effects) It would be nice to avoid calling doesFiberContain()570    // Maybe we can repurpose one of the subtreeFlags positions for this instead?571    // Use it to store which part of the tree the focused instance is in?572    // This assumes we can safely determine that instance during the "render" phase.573    if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {574      shouldFireAfterActiveInstanceBlur = true;575      beforeActiveInstanceBlur(deletion);576    }577  }578  if (isViewTransitionEligible) {579    commitExitViewTransitions(deletion);580  }581}582583function commitLayoutEffectOnFiber(584  finishedRoot: FiberRoot,585  current: Fiber | null,586  finishedWork: Fiber,587  committedLanes: Lanes,588): void {589  const prevEffectStart = pushComponentEffectStart();590  const prevEffectDuration = pushComponentEffectDuration();591  const prevEffectErrors = pushComponentEffectErrors();592  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();593  // When updating this function, also update reappearLayoutEffects, which does594  // most of the same things when an offscreen tree goes from hidden -> visible.595  const flags = finishedWork.flags;596  switch (finishedWork.tag) {597    case FunctionComponent:598    case ForwardRef:599    case SimpleMemoComponent: {600      recursivelyTraverseLayoutEffects(601        finishedRoot,602        finishedWork,603        committedLanes,604      );605      if (flags & Update) {606        commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);607      }608      break;609    }610    case ClassComponent: {611      recursivelyTraverseLayoutEffects(612        finishedRoot,613        finishedWork,614        committedLanes,615      );616      if (flags & Update) {617        commitClassLayoutLifecycles(finishedWork, current);618      }619620      if (flags & Callback) {621        commitClassCallbacks(finishedWork);622      }623624      if (flags & Ref) {625        safelyAttachRef(finishedWork, finishedWork.return);626      }627      break;628    }629    case HostRoot: {630      const prevProfilerEffectDuration = pushNestedEffectDurations();631      recursivelyTraverseLayoutEffects(632        finishedRoot,633        finishedWork,634        committedLanes,635      );636      if (flags & Callback) {637        commitRootCallbacks(finishedWork);638      }639      if (enableProfilerTimer && enableProfilerCommitHooks) {640        finishedRoot.effectDuration += popNestedEffectDurations(641          prevProfilerEffectDuration,642        );643      }644      break;645    }646    case HostSingleton: {647      if (supportsSingletons) {648        // We acquire the singleton instance first so it has appropriate649        // styles before other layout effects run. This isn't perfect because650        // an early sibling of the singleton may have an effect that can651        // observe the singleton before it is acquired.652        // @TODO move this to the mutation phase. The reason it isn't there yet653        // is it seemingly requires an extra traversal because we need to move the654        // disappear effect into a phase before the appear phase655        if (current === null && flags & Update) {656          // Unlike in the reappear path we only acquire on new mount657          commitHostSingletonAcquisition(finishedWork);658        }659        // We fall through to the HostComponent case below.660      }661      // Fallthrough662    }663    case HostHoistable:664    case HostComponent: {665      recursivelyTraverseLayoutEffects(666        finishedRoot,667        finishedWork,668        committedLanes,669      );670671      // Renderers may schedule work to be done after host components are mounted672      // (eg DOM renderer may schedule auto-focus for inputs and form controls).673      // These effects should only be committed when components are first mounted,674      // aka when there is no current/alternate.675      if (current === null) {676        if (flags & Update) {677          commitHostMount(finishedWork);678        } else if (flags & Hydrate) {679          commitHostHydratedInstance(finishedWork);680        }681      }682683      if (flags & Ref) {684        safelyAttachRef(finishedWork, finishedWork.return);685      }686      break;687    }688    case Profiler: {689      // TODO: Should this fire inside an offscreen tree? Or should it wait to690      // fire when the tree becomes visible again.691      if (flags & Update) {692        const prevProfilerEffectDuration = pushNestedEffectDurations();693694        recursivelyTraverseLayoutEffects(695          finishedRoot,696          finishedWork,697          committedLanes,698        );699700        const profilerInstance = finishedWork.stateNode;701702        if (enableProfilerTimer && enableProfilerCommitHooks) {703          // Propagate layout effect durations to the next nearest Profiler ancestor.704          // Do not reset these values until the next render so DevTools has a chance to read them first.705          profilerInstance.effectDuration += bubbleNestedEffectDurations(706            prevProfilerEffectDuration,707          );708        }709710        commitProfilerUpdate(711          finishedWork,712          current,713          commitStartTime,714          profilerInstance.effectDuration,715        );716      } else {717        recursivelyTraverseLayoutEffects(718          finishedRoot,719          finishedWork,720          committedLanes,721        );722      }723      break;724    }725    case ActivityComponent: {726      recursivelyTraverseLayoutEffects(727        finishedRoot,728        finishedWork,729        committedLanes,730      );731      if (flags & Update) {732        commitActivityHydrationCallbacks(finishedRoot, finishedWork);733      }734      break;735    }736    case SuspenseComponent: {737      recursivelyTraverseLayoutEffects(738        finishedRoot,739        finishedWork,740        committedLanes,741      );742      if (flags & Update) {743        commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);744      }745      if (flags & Callback) {746        // This Boundary is in fallback and has a dehydrated Suspense instance.747        // We could in theory assume the dehydrated state but we recheck it for748        // certainty.749        const finishedState: SuspenseState | null = finishedWork.memoizedState;750        if (finishedState !== null) {751          const dehydrated = finishedState.dehydrated;752          if (dehydrated !== null) {753            // Register a callback to retry this boundary once the server has sent the result.754            const retry = retryDehydratedSuspenseBoundary.bind(755              null,756              finishedWork,757            );758            registerSuspenseInstanceRetry(dehydrated, retry);759          }760        }761      }762      break;763    }764    case OffscreenComponent: {765      const isModernRoot =766        disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;767      if (isModernRoot) {768        const isHidden = finishedWork.memoizedState !== null;769        const newOffscreenSubtreeIsHidden =770          isHidden || offscreenSubtreeIsHidden;771        if (newOffscreenSubtreeIsHidden) {772          // The Offscreen tree is hidden. Skip over its layout effects.773        } else {774          // The Offscreen tree is visible.775776          const wasHidden = current !== null && current.memoizedState !== null;777          const newOffscreenSubtreeWasHidden =778            wasHidden || offscreenSubtreeWasHidden;779          const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;780          const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;781          offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;782          offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;783784          if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {785            // This is the root of a reappearing boundary. As we continue786            // traversing the layout effects, we must also re-mount layout787            // effects that were unmounted when the Offscreen subtree was788            // hidden. So this is a superset of the normal commitLayoutEffects.789            let layoutEffectTraversalFlags: LayoutEffectTraversalFlags;790            if (supportsSingletons) {791              layoutEffectTraversalFlags = IncludeHostSingletons;792            } else {793              layoutEffectTraversalFlags = NoLayoutEffectTraversalFlags;794            }795            if ((finishedWork.subtreeFlags & LayoutMask) !== NoFlags) {796              layoutEffectTraversalFlags |= IncludeWorkInProgressEffects;797            }798            recursivelyTraverseReappearLayoutEffects(799              finishedRoot,800              finishedWork,801              layoutEffectTraversalFlags,802            );803            if (804              enableProfilerTimer &&805              enableProfilerCommitHooks &&806              enableComponentPerformanceTrack &&807              (finishedWork.mode & ProfileMode) !== NoMode &&808              componentEffectStartTime >= 0 &&809              componentEffectEndTime >= 0 &&810              componentEffectEndTime - componentEffectStartTime > 0.05811            ) {812              logComponentReappeared(813                finishedWork,814                componentEffectStartTime,815                componentEffectEndTime,816              );817            }818          } else {819            recursivelyTraverseLayoutEffects(820              finishedRoot,821              finishedWork,822              committedLanes,823            );824          }825          offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;826          offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;827        }828      } else {829        recursivelyTraverseLayoutEffects(830          finishedRoot,831          finishedWork,832          committedLanes,833        );834      }835      break;836    }837    case ViewTransitionComponent: {838      if (enableViewTransition) {839        if (__DEV__) {840          if (flags & ViewTransitionNamedStatic) {841            trackNamedViewTransition(finishedWork);842          }843        }844        recursivelyTraverseLayoutEffects(845          finishedRoot,846          finishedWork,847          committedLanes,848        );849        if (flags & Ref) {850          safelyAttachRef(finishedWork, finishedWork.return);851        }852        break;853      }854      break;855    }856    case Fragment:857      if (flags & Ref) {858        safelyAttachRef(finishedWork, finishedWork.return);859      }860    // Fallthrough861    default: {862      recursivelyTraverseLayoutEffects(863        finishedRoot,864        finishedWork,865        committedLanes,866      );867      break;868    }869  }870871  if (872    enableProfilerTimer &&873    enableProfilerCommitHooks &&874    enableComponentPerformanceTrack &&875    (finishedWork.mode & ProfileMode) !== NoMode &&876    componentEffectStartTime >= 0 &&877    componentEffectEndTime >= 0878  ) {879    if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {880      logComponentEffect(881        finishedWork,882        componentEffectStartTime,883        componentEffectEndTime,884        componentEffectDuration,885        componentEffectErrors,886      );887    }888    if (889      // Insertion890      finishedWork.alternate === null &&891      finishedWork.return !== null &&892      finishedWork.return.alternate !== null &&893      componentEffectEndTime - componentEffectStartTime > 0.05894    ) {895      const isHydration = isHydratingParent(896        finishedWork.return.alternate,897        finishedWork.return,898      );899      if (!isHydration) {900        logComponentMount(901          finishedWork,902          componentEffectStartTime,903          componentEffectEndTime,904        );905      }906    }907  }908909  popComponentEffectStart(prevEffectStart);910  popComponentEffectDuration(prevEffectDuration);911  popComponentEffectErrors(prevEffectErrors);912  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);913}914915function abortRootTransitions(916  root: FiberRoot,917  abort: TransitionAbort,918  deletedTransitions: Set<Transition>,919  deletedOffscreenInstance: OffscreenInstance | null,920  isInDeletedTree: boolean,921) {922  if (enableTransitionTracing) {923    const rootTransitions = root.incompleteTransitions;924    deletedTransitions.forEach(transition => {925      if (rootTransitions.has(transition)) {926        const transitionInstance: TracingMarkerInstance = rootTransitions.get(927          transition,928        ) as any;929        if (transitionInstance.aborts === null) {930          transitionInstance.aborts = [];931        }932        transitionInstance.aborts.push(abort);933934        if (deletedOffscreenInstance !== null) {935          if (936            transitionInstance.pendingBoundaries !== null &&937            transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)938          ) {939            // $FlowFixMe[incompatible-use] found when upgrading Flow940            transitionInstance.pendingBoundaries.delete(941              deletedOffscreenInstance,942            );943          }944        }945      }946    });947  }948}949950function abortTracingMarkerTransitions(951  abortedFiber: Fiber,952  abort: TransitionAbort,953  deletedTransitions: Set<Transition>,954  deletedOffscreenInstance: OffscreenInstance | null,955  isInDeletedTree: boolean,956) {957  if (enableTransitionTracing) {958    const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;959    const markerTransitions = markerInstance.transitions;960    const pendingBoundaries = markerInstance.pendingBoundaries;961    if (markerTransitions !== null) {962      // TODO: Refactor this code. Is there a way to move this code to963      // the deletions phase instead of calculating it here while making sure964      // complete is called appropriately?965      deletedTransitions.forEach(transition => {966        // If one of the transitions on the tracing marker is a transition967        // that was in an aborted subtree, we will abort that tracing marker968        if (969          // $FlowFixMe[invalid-compare]970          abortedFiber !== null &&971          markerTransitions.has(transition) &&972          (markerInstance.aborts === null ||973            !markerInstance.aborts.includes(abort))974        ) {975          if (markerInstance.transitions !== null) {976            if (markerInstance.aborts === null) {977              markerInstance.aborts = [abort];978              addMarkerIncompleteCallbackToPendingTransition(979                abortedFiber.memoizedProps.name,980                markerInstance.transitions,981                markerInstance.aborts,982              );983            } else {984              markerInstance.aborts.push(abort);985            }986987            // We only want to call onTransitionProgress when the marker hasn't been988            // deleted989            if (990              deletedOffscreenInstance !== null &&991              !isInDeletedTree &&992              pendingBoundaries !== null &&993              pendingBoundaries.has(deletedOffscreenInstance)994            ) {995              pendingBoundaries.delete(deletedOffscreenInstance);996997              addMarkerProgressCallbackToPendingTransition(998                abortedFiber.memoizedProps.name,999                deletedTransitions,1000                pendingBoundaries,1001              );1002            }1003          }1004        }1005      });1006    }1007  }1008}10091010function abortParentMarkerTransitionsForDeletedFiber(1011  abortedFiber: Fiber,1012  abort: TransitionAbort,1013  deletedTransitions: Set<Transition>,1014  deletedOffscreenInstance: OffscreenInstance | null,1015  isInDeletedTree: boolean,1016) {1017  if (enableTransitionTracing) {1018    // Find all pending markers that are waiting on child suspense boundaries in the1019    // aborted subtree and cancels them1020    let fiber: null | Fiber = abortedFiber;1021    while (fiber !== null) {1022      switch (fiber.tag) {1023        case TracingMarkerComponent:1024          abortTracingMarkerTransitions(1025            fiber,1026            abort,1027            deletedTransitions,1028            deletedOffscreenInstance,1029            isInDeletedTree,1030          );1031          break;1032        case HostRoot:1033          const root = fiber.stateNode;1034          abortRootTransitions(1035            root,1036            abort,1037            deletedTransitions,1038            deletedOffscreenInstance,1039            isInDeletedTree,1040          );10411042          break;1043        default:1044          break;1045      }10461047      fiber = fiber.return;1048    }1049  }1050}10511052function commitTransitionProgress(offscreenFiber: Fiber) {1053  if (enableTransitionTracing) {1054    // This function adds suspense boundaries to the root1055    // or tracing marker's pendingBoundaries map.1056    // When a suspense boundary goes from a resolved to a fallback1057    // state we add the boundary to the map, and when it goes from1058    // a fallback to a resolved state, we remove the boundary from1059    // the map.10601061    // We use stateNode on the Offscreen component as a stable object1062    // that doesnt change from render to render. This way we can1063    // distinguish between different Offscreen instances (vs. the same1064    // Offscreen instance with different fibers)1065    const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10661067    let prevState: SuspenseState | null = null;1068    const previousFiber = offscreenFiber.alternate;1069    if (previousFiber !== null && previousFiber.memoizedState !== null) {1070      prevState = previousFiber.memoizedState;1071    }1072    const nextState: SuspenseState | null = offscreenFiber.memoizedState;10731074    const wasHidden = prevState !== null;1075    const isHidden = nextState !== null;10761077    const pendingMarkers = offscreenInstance._pendingMarkers;1078    // If there is a name on the suspense boundary, store that in1079    // the pending boundaries.1080    let name = null;1081    const parent = offscreenFiber.return;1082    if (1083      parent !== null &&1084      parent.tag === SuspenseComponent &&1085      parent.memoizedProps.name1086    ) {1087      name = parent.memoizedProps.name;1088    }10891090    if (!wasHidden && isHidden) {1091      // The suspense boundaries was just hidden. Add the boundary1092      // to the pending boundary set if it's there1093      if (pendingMarkers !== null) {1094        pendingMarkers.forEach(markerInstance => {1095          const pendingBoundaries = markerInstance.pendingBoundaries;1096          const transitions = markerInstance.transitions;1097          const markerName = markerInstance.name;1098          if (1099            pendingBoundaries !== null &&1100            !pendingBoundaries.has(offscreenInstance)1101          ) {1102            pendingBoundaries.set(offscreenInstance, {1103              name,1104            });1105            if (transitions !== null) {1106              if (1107                markerInstance.tag === TransitionTracingMarker &&1108                markerName !== null1109              ) {1110                addMarkerProgressCallbackToPendingTransition(1111                  markerName,1112                  transitions,1113                  pendingBoundaries,1114                );1115              } else if (markerInstance.tag === TransitionRoot) {1116                transitions.forEach(transition => {1117                  addTransitionProgressCallbackToPendingTransition(1118                    transition,1119                    pendingBoundaries,1120                  );1121                });1122              }1123            }1124          }1125        });1126      }1127    } else if (wasHidden && !isHidden) {1128      // The suspense boundary went from hidden to visible. Remove1129      // the boundary from the pending suspense boundaries set1130      // if it's there1131      if (pendingMarkers !== null) {1132        pendingMarkers.forEach(markerInstance => {1133          const pendingBoundaries = markerInstance.pendingBoundaries;1134          const transitions = markerInstance.transitions;1135          const markerName = markerInstance.name;1136          if (1137            pendingBoundaries !== null &&1138            pendingBoundaries.has(offscreenInstance)1139          ) {1140            pendingBoundaries.delete(offscreenInstance);1141            if (transitions !== null) {1142              if (1143                markerInstance.tag === TransitionTracingMarker &&1144                markerName !== null1145              ) {1146                addMarkerProgressCallbackToPendingTransition(1147                  markerName,1148                  transitions,1149                  pendingBoundaries,1150                );11511152                // If there are no more unresolved suspense boundaries, the interaction1153                // is considered finished1154                if (pendingBoundaries.size === 0) {1155                  if (markerInstance.aborts === null) {1156                    addMarkerCompleteCallbackToPendingTransition(1157                      markerName,1158                      transitions,1159                    );1160                  }1161                  markerInstance.transitions = null;1162                  markerInstance.pendingBoundaries = null;1163                  markerInstance.aborts = null;1164                }1165              } else if (markerInstance.tag === TransitionRoot) {1166                transitions.forEach(transition => {1167                  addTransitionProgressCallbackToPendingTransition(1168                    transition,1169                    pendingBoundaries,1170                  );1171                });1172              }1173            }1174          }1175        });1176      }1177    }1178  }1179}11801181function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {1182  if (!supportsMutation) {1183    return;1184  }1185  // Finds the nearest host component children and updates their visibility1186  // to either hidden or visible.1187  let child = parentFiber.child;1188  while (child !== null) {1189    hideOrUnhideAllChildrenOnFiber(child, isHidden);1190    child = child.sibling;1191  }1192}11931194function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {1195  if (!supportsMutation) {1196    return;1197  }1198  switch (fiber.tag) {1199    case HostComponent:1200    case HostHoistable: {1201      // Found the nearest host component. Hide it.1202      commitShowHideHostInstance(fiber, isHidden);1203      // Typically, only the nearest host nodes need to be hidden, since that1204      // has the effect of also hiding everything inside of them.1205      //1206      // However, there's a special case for portals, because portals do not1207      // exist in the regular host tree hierarchy; we can't assume that just1208      // because a portal's HostComponent parent in the React tree will also be1209      // a parent in the actual host tree.1210      //1211      // So, if any portals exist within the tree, regardless of how deeply1212      // nested they are, we need to repeat this algorithm for its children.1213      hideOrUnhideNearestPortals(fiber, isHidden);1214      return;1215    }1216    case HostText: {1217      commitShowHideHostTextInstance(fiber, isHidden);1218      return;1219    }1220    case DehydratedFragment: {1221      commitShowHideSuspenseBoundary(fiber, isHidden);1222      return;1223    }1224    case OffscreenComponent:1225    case LegacyHiddenComponent: {1226      const offscreenState: OffscreenState | null = fiber.memoizedState;1227      if (offscreenState !== null) {1228        // Found a nested Offscreen component that is hidden.1229        // Don't search any deeper. This tree should remain hidden.1230      } else {1231        hideOrUnhideAllChildren(fiber, isHidden);1232      }1233      return;1234    }1235    default: {1236      hideOrUnhideAllChildren(fiber, isHidden);1237      return;1238    }1239  }1240}12411242function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {1243  if (!supportsMutation) {1244    return;1245  }1246  if (parentFiber.subtreeFlags & PortalStatic) {1247    let child = parentFiber.child;1248    while (child !== null) {1249      hideOrUnhideNearestPortalsOnFiber(child, isHidden);1250      child = child.sibling;1251    }1252  }1253}12541255function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {1256  if (!supportsMutation) {1257    return;1258  }1259  switch (fiber.tag) {1260    case HostPortal: {1261      // Found a portal. Switch back to the normal hide/unhide algorithm to1262      // toggle the visibility of its children.1263      hideOrUnhideAllChildrenOnFiber(fiber, isHidden);1264      return;1265    }1266    case OffscreenComponent: {1267      const offscreenState: OffscreenState | null = fiber.memoizedState;1268      if (offscreenState !== null) {1269        // Found a nested Offscreen component that is hidden. Don't search any1270        // deeper. This tree should remain hidden.1271      } else {1272        hideOrUnhideNearestPortals(fiber, isHidden);1273      }1274      return;1275    }1276    default: {1277      hideOrUnhideNearestPortals(fiber, isHidden);1278      return;1279    }1280  }1281}12821283function detachFiberMutation(fiber: Fiber) {1284  // Cut off the return pointer to disconnect it from the tree.1285  // This enables us to detect and warn against state updates on an unmounted component.1286  // It also prevents events from bubbling from within disconnected components.1287  //1288  // Ideally, we should also clear the child pointer of the parent alternate to let this1289  // get GC:ed but we don't know which for sure which parent is the current1290  // one so we'll settle for GC:ing the subtree of this child.1291  // This child itself will be GC:ed when the parent updates the next time.1292  //1293  // Note that we can't clear child or sibling pointers yet.1294  // They're needed for passive effects and for findDOMNode.1295  // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).1296  //1297  // Don't reset the alternate yet, either. We need that so we can detach the1298  // alternate's fields in the passive phase. Clearing the return pointer is1299  // sufficient for findDOMNode semantics.1300  const alternate = fiber.alternate;1301  if (alternate !== null) {1302    alternate.return = null;1303  }1304  fiber.return = null;1305}13061307function detachFiberAfterEffects(fiber: Fiber) {1308  const alternate = fiber.alternate;1309  if (alternate !== null) {1310    fiber.alternate = null;1311    detachFiberAfterEffects(alternate);1312  }13131314  // Clear cyclical Fiber fields. This level alone is designed to roughly1315  // approximate the planned Fiber refactor. In that world, `setState` will be1316  // bound to a special "instance" object instead of a Fiber. The Instance1317  // object will not have any of these fields. It will only be connected to1318  // the fiber tree via a single link at the root. So if this level alone is1319  // sufficient to fix memory issues, that bodes well for our plans.1320  fiber.child = null;1321  fiber.deletions = null;1322  fiber.sibling = null;13231324  // The `stateNode` is cyclical because on host nodes it points to the host1325  // tree, which has its own pointers to children, parents, and siblings.1326  // The other host nodes also point back to fibers, so we should detach that1327  // one, too.1328  if (fiber.tag === HostComponent) {1329    const hostInstance: Instance = fiber.stateNode;1330    // $FlowFixMe[invalid-compare]1331    if (hostInstance !== null) {1332      detachDeletedInstance(hostInstance);1333    }1334  }1335  fiber.stateNode = null;13361337  if (__DEV__) {1338    fiber._debugOwner = null;1339  }13401341  // Theoretically, nothing in here should be necessary, because we already1342  // disconnected the fiber from the tree. So even if something leaks this1343  // particular fiber, it won't leak anything else.1344  fiber.return = null;1345  fiber.dependencies = null;1346  fiber.memoizedProps = null;1347  fiber.memoizedState = null;1348  fiber.pendingProps = null;1349  fiber.stateNode = null;1350  // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.1351  fiber.updateQueue = null;1352}13531354// These are tracked on the stack as we recursively traverse a1355// deleted subtree.1356// TODO: Update these during the whole mutation phase, not just during1357// a deletion.1358let hostParent: Instance | Container | null = null;1359let hostParentIsContainer: boolean = false;13601361function commitDeletionEffects(1362  root: FiberRoot,1363  returnFiber: Fiber,1364  deletedFiber: Fiber,1365) {1366  const prevEffectStart = pushComponentEffectStart();13671368  if (supportsMutation) {1369    // We only have the top Fiber that was deleted but we need to recurse down its1370    // children to find all the terminal nodes.13711372    // Recursively delete all host nodes from the parent, detach refs, clean1373    // up mounted layout effects, and call componentWillUnmount.13741375    // We only need to remove the topmost host child in each branch. But then we1376    // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1377    // could split this into two separate traversals functions, where the second1378    // one doesn't include any removeChild logic. This is maybe the same1379    // function as "disappearLayoutEffects" (or whatever that turns into after1380    // the layout phase is refactored to use recursion).13811382    // Before starting, find the nearest host parent on the stack so we know1383    // which instance/container to remove the children from.1384    // TODO: Instead of searching up the fiber return path on every deletion, we1385    // can track the nearest host component on the JS stack as we traverse the1386    // tree during the commit phase. This would make insertions faster, too.1387    let parent: null | Fiber = returnFiber;1388    findParent: while (parent !== null) {1389      switch (parent.tag) {1390        case HostSingleton: {1391          if (supportsSingletons) {1392            if (isSingletonScope(parent.type)) {1393              hostParent = parent.stateNode;1394              hostParentIsContainer = false;1395              break findParent;1396            }1397            break;1398          }1399          // Expected fallthrough when supportsSingletons is false1400        }1401        case HostComponent: {1402          hostParent = parent.stateNode;1403          hostParentIsContainer = false;1404          break findParent;1405        }1406        case HostRoot:1407        case HostPortal: {1408          hostParent = parent.stateNode.containerInfo;1409          hostParentIsContainer = true;1410          break findParent;1411        }1412      }1413      parent = parent.return;1414    }1415    if (hostParent === null) {1416      throw new Error(1417        'Expected to find a host parent. This error is likely caused by ' +1418          'a bug in React. Please file an issue.',1419      );1420    }14211422    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1423    hostParent = null;1424    hostParentIsContainer = false;1425  } else {1426    // Detach refs and call componentWillUnmount() on the whole subtree.1427    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1428  }14291430  if (1431    enableProfilerTimer &&1432    enableProfilerCommitHooks &&1433    enableComponentPerformanceTrack &&1434    (deletedFiber.mode & ProfileMode) !== NoMode &&1435    componentEffectStartTime >= 0 &&1436    componentEffectEndTime >= 0 &&1437    componentEffectEndTime - componentEffectStartTime > 0.051438  ) {1439    logComponentUnmount(1440      deletedFiber,1441      componentEffectStartTime,1442      componentEffectEndTime,1443    );1444  }1445  popComponentEffectStart(prevEffectStart);14461447  detachFiberMutation(deletedFiber);1448}14491450function recursivelyTraverseDeletionEffects(1451  finishedRoot: FiberRoot,1452  nearestMountedAncestor: Fiber,1453  parent: Fiber,1454) {1455  // TODO: Use a static flag to skip trees that don't have unmount effects1456  let child = parent.child;1457  while (child !== null) {1458    commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1459    child = child.sibling;1460  }1461}14621463function commitDeletionEffectsOnFiber(1464  finishedRoot: FiberRoot,1465  nearestMountedAncestor: Fiber,1466  deletedFiber: Fiber,1467) {1468  // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.1469  onCommitUnmount(deletedFiber);14701471  const prevEffectStart = pushComponentEffectStart();1472  const prevEffectDuration = pushComponentEffectDuration();1473  const prevEffectErrors = pushComponentEffectErrors();1474  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();14751476  // The cases in this outer switch modify the stack before they traverse1477  // into their subtree. There are simpler cases in the inner switch1478  // that don't modify the stack.1479  switch (deletedFiber.tag) {1480    case HostHoistable: {1481      if (supportsResources) {1482        if (!offscreenSubtreeWasHidden) {1483          safelyDetachRef(deletedFiber, nearestMountedAncestor);1484        }1485        recursivelyTraverseDeletionEffects(1486          finishedRoot,1487          nearestMountedAncestor,1488          deletedFiber,1489        );1490        if (deletedFiber.memoizedState) {1491          releaseResource(deletedFiber.memoizedState);1492        } else if (deletedFiber.stateNode) {1493          // A Hoistable Instance lives in document.head only when its enclosing1494          // Activity is visible. If the Activity is hidden (or has been hidden1495          // since mount), the instance was either never inserted or was1496          // detached by the disappear traversal. Skip in those cases.1497          if (!offscreenSubtreeWasHidden) {1498            unmountHoistable(deletedFiber.stateNode);1499          }1500        }1501        break;1502      }1503      // Fall through1504    }1505    case HostSingleton: {1506      if (supportsSingletons) {1507        if (!offscreenSubtreeWasHidden) {1508          safelyDetachRef(deletedFiber, nearestMountedAncestor);1509        }1510        commitFragmentInstanceDeletionEffects(deletedFiber);15111512        const prevHostParent = hostParent;1513        const prevHostParentIsContainer = hostParentIsContainer;1514        if (isSingletonScope(deletedFiber.type)) {1515          hostParent = deletedFiber.stateNode;1516          hostParentIsContainer = false;1517        }1518        recursivelyTraverseDeletionEffects(1519          finishedRoot,1520          nearestMountedAncestor,1521          deletedFiber,1522        );15231524        // Normally this is called in passive unmount effect phase however with1525        // HostSingleton we warn if you acquire one that is already associated to1526        // a different fiber. To increase our chances of avoiding this, specifically1527        // if you keyed a HostSingleton so there will be a delete followed by a Placement1528        // we treat detach eagerly here1529        commitHostSingletonRelease(deletedFiber);15301531        hostParent = prevHostParent;1532        hostParentIsContainer = prevHostParentIsContainer;15331534        break;1535      }1536      // Fall through1537    }1538    case HostComponent: {1539      if (!offscreenSubtreeWasHidden) {1540        safelyDetachRef(deletedFiber, nearestMountedAncestor);1541      }1542      commitFragmentInstanceDeletionEffects(deletedFiber);1543      // Intentional fallthrough to next branch1544    }1545    case HostText: {1546      // HostComponent falls through into this case.1547      if (deletedFiber.tag === HostText) {1548        commitFragmentInstanceDeletionEffects(deletedFiber);1549      }1550      // We only need to remove the nearest host child. Set the host parent1551      // to `null` on the stack to indicate that nested children don't1552      // need to be removed.1553      if (supportsMutation) {1554        const prevHostParent = hostParent;1555        const prevHostParentIsContainer = hostParentIsContainer;1556        hostParent = null;1557        recursivelyTraverseDeletionEffects(1558          finishedRoot,1559          nearestMountedAncestor,1560          deletedFiber,1561        );1562        hostParent = prevHostParent;1563        hostParentIsContainer = prevHostParentIsContainer;15641565        if (hostParent !== null) {1566          // Now that all the child effects have unmounted, we can remove the1567          // node from the tree.1568          if (hostParentIsContainer) {1569            commitHostRemoveChildFromContainer(1570              deletedFiber,1571              nearestMountedAncestor,1572              hostParent as any as Container,1573              deletedFiber.stateNode as Instance | TextInstance,1574            );1575          } else {1576            commitHostRemoveChild(1577              deletedFiber,1578              nearestMountedAncestor,1579              hostParent as any as Instance,1580              deletedFiber.stateNode as Instance | TextInstance,1581            );1582          }1583        }1584      } else {1585        recursivelyTraverseDeletionEffects(1586          finishedRoot,1587          nearestMountedAncestor,1588          deletedFiber,1589        );1590      }1591      break;1592    }1593    case DehydratedFragment: {1594      if (enableSuspenseCallback) {1595        const hydrationCallbacks = finishedRoot.hydrationCallbacks;1596        if (hydrationCallbacks !== null) {1597          try {1598            const onDeleted = hydrationCallbacks.onDeleted;1599            if (onDeleted) {1600              onDeleted(1601                deletedFiber.stateNode as SuspenseInstance | ActivityInstance,1602              );1603            }1604          } catch (error) {1605            captureCommitPhaseError(1606              deletedFiber,1607              nearestMountedAncestor,1608              error,1609            );1610          }1611        }1612      }16131614      // Dehydrated fragments don't have any children16151616      // Delete the dehydrated suspense boundary and all of its content.1617      if (supportsMutation) {1618        if (hostParent !== null) {1619          if (hostParentIsContainer) {1620            clearSuspenseBoundaryFromContainer(1621              hostParent as any as Container,1622              deletedFiber.stateNode as SuspenseInstance,1623            );1624          } else {1625            clearSuspenseBoundary(1626              hostParent as any as Instance,1627              deletedFiber.stateNode as SuspenseInstance,1628            );1629          }1630        }1631      }1632      break;1633    }1634    case HostPortal: {1635      if (supportsMutation) {1636        // When we go into a portal, it becomes the parent to remove from.1637        const prevHostParent = hostParent;1638        const prevHostParentIsContainer = hostParentIsContainer;1639        hostParent = deletedFiber.stateNode.containerInfo;1640        hostParentIsContainer = true;1641        recursivelyTraverseDeletionEffects(1642          finishedRoot,1643          nearestMountedAncestor,1644          deletedFiber,1645        );1646        hostParent = prevHostParent;1647        hostParentIsContainer = prevHostParentIsContainer;1648      } else {1649        if (supportsPersistence) {1650          commitHostPortalContainerChildren(1651            deletedFiber.stateNode,1652            deletedFiber,1653            createContainerChildSet(),1654          );1655        }16561657        recursivelyTraverseDeletionEffects(1658          finishedRoot,1659          nearestMountedAncestor,1660          deletedFiber,1661        );1662      }1663      break;1664    }1665    case FunctionComponent:1666    case ForwardRef:1667    case MemoComponent:1668    case SimpleMemoComponent: {1669      // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.1670      commitHookEffectListUnmount(1671        HookInsertion,1672        deletedFiber,1673        nearestMountedAncestor,1674      );1675      if (!offscreenSubtreeWasHidden) {1676        commitHookLayoutUnmountEffects(1677          deletedFiber,1678          nearestMountedAncestor,1679          HookLayout,1680        );1681      }1682      recursivelyTraverseDeletionEffects(1683        finishedRoot,1684        nearestMountedAncestor,1685        deletedFiber,1686      );1687      break;1688    }1689    case ClassComponent: {1690      if (!offscreenSubtreeWasHidden) {1691        safelyDetachRef(deletedFiber, nearestMountedAncestor);1692        const instance = deletedFiber.stateNode;1693        if (typeof instance.componentWillUnmount === 'function') {1694          safelyCallComponentWillUnmount(1695            deletedFiber,1696            nearestMountedAncestor,1697            instance,1698          );1699        }1700      }1701      recursivelyTraverseDeletionEffects(1702        finishedRoot,1703        nearestMountedAncestor,1704        deletedFiber,1705      );1706      break;1707    }1708    case ScopeComponent: {1709      if (enableScopeAPI) {1710        if (!offscreenSubtreeWasHidden) {1711          safelyDetachRef(deletedFiber, nearestMountedAncestor);1712        }1713      }1714      recursivelyTraverseDeletionEffects(1715        finishedRoot,1716        nearestMountedAncestor,1717        deletedFiber,1718      );1719      break;1720    }1721    case OffscreenComponent: {1722      if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {1723        // If this offscreen component is hidden, we already unmounted it. Before1724        // deleting the children, track that it's already unmounted so that we1725        // don't attempt to unmount the effects again.1726        // TODO: If the tree is hidden, in most cases we should be able to skip1727        // over the nested children entirely. An exception is we haven't yet found1728        // the topmost host node to delete, which we already track on the stack.1729        // But the other case is portals, which need to be detached no matter how1730        // deeply they are nested. We should use a subtree flag to track whether a1731        // subtree includes a nested portal.1732        const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1733        offscreenSubtreeWasHidden =1734          prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17351736        recursivelyTraverseDeletionEffects(1737          finishedRoot,1738          nearestMountedAncestor,1739          deletedFiber,1740        );1741        offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;1742      } else {1743        recursivelyTraverseDeletionEffects(1744          finishedRoot,1745          nearestMountedAncestor,1746          deletedFiber,1747        );1748      }1749      break;1750    }1751    case ViewTransitionComponent: {1752      if (enableViewTransition) {1753        if (__DEV__) {1754          if (deletedFiber.flags & ViewTransitionNamedStatic) {1755            untrackNamedViewTransition(deletedFiber);1756          }1757        }1758        safelyDetachRef(deletedFiber, nearestMountedAncestor);1759        recursivelyTraverseDeletionEffects(1760          finishedRoot,1761          nearestMountedAncestor,1762          deletedFiber,1763        );1764        break;1765      }1766      // Fallthrough1767    }1768    case Fragment: {1769      if (!offscreenSubtreeWasHidden) {1770        safelyDetachRef(deletedFiber, nearestMountedAncestor);1771      }1772      recursivelyTraverseDeletionEffects(1773        finishedRoot,1774        nearestMountedAncestor,1775        deletedFiber,1776      );1777      break;1778    }1779    default: {1780      recursivelyTraverseDeletionEffects(1781        finishedRoot,1782        nearestMountedAncestor,1783        deletedFiber,1784      );1785      break;1786    }1787  }17881789  if (1790    enableProfilerTimer &&1791    enableProfilerCommitHooks &&1792    enableComponentPerformanceTrack &&1793    (deletedFiber.mode & ProfileMode) !== NoMode &&1794    componentEffectStartTime >= 0 &&1795    componentEffectEndTime >= 0 &&1796    (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)1797  ) {1798    logComponentEffect(1799      deletedFiber,1800      componentEffectStartTime,1801      componentEffectEndTime,1802      componentEffectDuration,1803      componentEffectErrors,1804    );1805  }18061807  popComponentEffectStart(prevEffectStart);1808  popComponentEffectDuration(prevEffectDuration);1809  popComponentEffectErrors(prevEffectErrors);1810  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1811}18121813function commitSuspenseCallback(finishedWork: Fiber) {1814  // TODO: Delete this feature. It's not properly covered by DEV features.1815  const newState: SuspenseState | null = finishedWork.memoizedState;1816  if (enableSuspenseCallback && newState !== null) {1817    const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;1818    if (typeof suspenseCallback === 'function') {1819      const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;1820      if (retryQueue !== null) {1821        suspenseCallback(new Set(retryQueue));1822      }1823    } else if (__DEV__) {1824      if (suspenseCallback !== undefined) {1825        console.error('Unexpected type for suspenseCallback.');1826      }1827    }1828  }1829}18301831function commitActivityHydrationCallbacks(1832  finishedRoot: FiberRoot,1833  finishedWork: Fiber,1834) {1835  if (!supportsHydration) {1836    return;1837  }1838  const newState: ActivityState | null = finishedWork.memoizedState;1839  if (newState === null) {1840    const current = finishedWork.alternate;1841    if (current !== null) {1842      const prevState: ActivityState | null = current.memoizedState;1843      if (prevState !== null) {1844        const activityInstance = prevState.dehydrated;1845        commitHostHydratedActivity(activityInstance, finishedWork);1846        if (enableSuspenseCallback) {1847          try {1848            // TODO: Delete this feature. It's not properly covered by DEV features.1849            const hydrationCallbacks = finishedRoot.hydrationCallbacks;1850            if (hydrationCallbacks !== null) {1851              const onHydrated = hydrationCallbacks.onHydrated;1852              if (onHydrated) {1853                onHydrated(activityInstance);1854              }1855            }1856          } catch (error) {1857            captureCommitPhaseError(finishedWork, finishedWork.return, error);1858          }1859        }1860      }1861    }1862  }1863}18641865function commitSuspenseHydrationCallbacks(1866  finishedRoot: FiberRoot,1867  finishedWork: Fiber,1868) {1869  if (!supportsHydration) {1870    return;1871  }1872  const newState: SuspenseState | null = finishedWork.memoizedState;1873  if (newState === null) {1874    const current = finishedWork.alternate;1875    if (current !== null) {1876      const prevState: SuspenseState | null = current.memoizedState;1877      if (prevState !== null) {1878        const suspenseInstance = prevState.dehydrated;1879        if (suspenseInstance !== null) {1880          commitHostHydratedSuspense(suspenseInstance, finishedWork);1881          if (enableSuspenseCallback) {1882            try {1883              // TODO: Delete this feature. It's not properly covered by DEV features.1884              const hydrationCallbacks = finishedRoot.hydrationCallbacks;1885              if (hydrationCallbacks !== null) {1886                const onHydrated = hydrationCallbacks.onHydrated;1887                if (onHydrated) {1888                  onHydrated(suspenseInstance);1889                }1890              }1891            } catch (error) {1892              captureCommitPhaseError(finishedWork, finishedWork.return, error);1893            }1894          }1895        }1896      }1897    }1898  }1899}19001901function getRetryCache(finishedWork: Fiber) {1902  // TODO: Unify the interface for the retry cache so we don't have to switch1903  // on the tag like this.1904  switch (finishedWork.tag) {1905    case ActivityComponent:1906    case SuspenseComponent:1907    case SuspenseListComponent: {1908      let retryCache = finishedWork.stateNode;1909      if (retryCache === null) {1910        retryCache = finishedWork.stateNode = new PossiblyWeakSet();1911      }1912      return retryCache;1913    }1914    case OffscreenComponent: {1915      const instance: OffscreenInstance = finishedWork.stateNode;1916      let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =1917        instance._retryCache;1918      if (retryCache === null) {1919        retryCache = instance._retryCache = new PossiblyWeakSet();1920      }1921      return retryCache;1922    }1923    default: {1924      throw new Error(1925        `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +1926          'bug in React.',1927      );1928    }1929  }1930}19311932function attachSuspenseRetryListeners(1933  finishedWork: Fiber,1934  wakeables: RetryQueue,1935) {1936  // If this boundary just timed out, then it will have a set of wakeables.1937  // For each wakeable, attach a listener so that when it resolves, React1938  // attempts to re-render the boundary in the primary (pre-timeout) state.1939  const retryCache = getRetryCache(finishedWork);1940  wakeables.forEach(wakeable => {1941    // Memoize using the boundary fiber to prevent redundant listeners.1942    if (!retryCache.has(wakeable)) {1943      retryCache.add(wakeable);19441945      if (enableUpdaterTracking) {1946        if (isDevToolsPresent) {1947          if (inProgressLanes !== null && inProgressRoot !== null) {1948            // If we have pending work still, associate the original updaters with it.1949            restorePendingUpdaters(inProgressRoot, inProgressLanes);1950          } else {1951            throw Error(1952              'Expected finished root and lanes to be set. This is a bug in React.',1953            );1954          }1955        }1956      }19571958      const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);1959      wakeable.then(retry, retry);1960    }1961  });1962}19631964// This function detects when a Suspense boundary goes from visible to hidden.1965// It returns false if the boundary is already hidden.1966// TODO: Use an effect tag.1967function isSuspenseBoundaryBeingHidden(1968  current: Fiber | null,1969  finishedWork: Fiber,1970): boolean {1971  if (current !== null) {1972    const oldState: SuspenseState | null = current.memoizedState;1973    if (oldState === null || oldState.dehydrated !== null) {1974      const newState: SuspenseState | null = finishedWork.memoizedState;1975      return newState !== null && newState.dehydrated === null;1976    }1977  }1978  return false;1979}19801981export function commitMutationEffects(1982  root: FiberRoot,1983  finishedWork: Fiber,1984  committedLanes: Lanes,1985) {1986  inProgressLanes = committedLanes;1987  inProgressRoot = root;19881989  rootViewTransitionAffected = false;1990  inUpdateViewTransition = false;19911992  resetComponentEffectTimers();19931994  commitMutationEffectsOnFiber(finishedWork, root, committedLanes);19951996  inProgressLanes = null;1997  inProgressRoot = null;1998}19992000function recursivelyTraverseMutationEffects(

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.

Generated by searchcode.com · powered by code_get_file + code_get_findings