packages/react-reconciler/src/ReactFiberCommitWork.js JAVASCRIPT 5,619 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,619.
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  createHoistableInstance,173  mountHoistable,174  unmountHoistable,175  prepareToCommitHoistables,176  maySuspendCommitInSyncRender,177  suspendInstance,178  suspendResource,179  resetFormInstance,180  registerSuspenseInstanceRetry,181  cancelViewTransitionName,182  cancelRootViewTransitionName,183  restoreRootViewTransitionName,184  isSingletonScope,185  updateFragmentInstanceFiber,186} from './ReactFiberConfig';187import {188  captureCommitPhaseError,189  resolveRetryWakeable,190  markCommitTimeOfFallback,191  restorePendingUpdaters,192  addTransitionStartCallbackToPendingTransition,193  addTransitionProgressCallbackToPendingTransition,194  addTransitionCompleteCallbackToPendingTransition,195  addMarkerProgressCallbackToPendingTransition,196  addMarkerIncompleteCallbackToPendingTransition,197  addMarkerCompleteCallbackToPendingTransition,198  retryDehydratedSuspenseBoundary,199  scheduleViewTransitionEvent,200} from './ReactFiberWorkLoop';201import {202  HasEffect as HookHasEffect,203  Layout as HookLayout,204  Insertion as HookInsertion,205  Passive as HookPassive,206} from './ReactHookEffectTags';207import {doesFiberContain} from './ReactFiberTreeReflection';208import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';209import {releaseCache, retainCache} from './ReactFiberCacheComponent';210import {clearTransitionsForLanes} from './ReactFiberLane';211import {212  OffscreenVisible,213  OffscreenPassiveEffectsConnected,214} from './ReactFiberOffscreenComponent';215import {216  TransitionRoot,217  TransitionTracingMarker,218} from './ReactFiberTracingMarkerComponent';219import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';220import {221  commitHookLayoutEffects,222  commitHookLayoutUnmountEffects,223  commitHookEffectListMount,224  commitHookEffectListUnmount,225  commitHookPassiveMountEffects,226  commitHookPassiveUnmountEffects,227  commitClassLayoutLifecycles,228  commitClassDidMount,229  commitClassCallbacks,230  commitClassHiddenCallbacks,231  commitClassSnapshot,232  safelyCallComponentWillUnmount,233  safelyAttachRef,234  safelyDetachRef,235  commitProfilerUpdate,236  commitProfilerPostCommit,237  commitRootCallbacks,238} from './ReactFiberCommitEffects';239import {240  commitHostMount,241  commitHostHydratedInstance,242  commitHostUpdate,243  commitHostTextUpdate,244  commitHostResetTextContent,245  commitShowHideSuspenseBoundary,246  commitShowHideHostInstance,247  commitShowHideHostTextInstance,248  commitHostPlacement,249  commitHostRootContainerChildren,250  commitHostPortalContainerChildren,251  commitHostHydratedContainer,252  commitHostHydratedActivity,253  commitHostHydratedSuspense,254  commitHostRemoveChildFromContainer,255  commitHostRemoveChild,256  commitHostSingletonAcquisition,257  commitHostSingletonRelease,258} from './ReactFiberCommitHostEffects';259import {260  commitFragmentInstanceDeletionEffects,261  commitFragmentInstanceInsertionEffects,262} from './ReactFiberFragmentInstance';263import {264  trackEnterViewTransitions,265  commitEnterViewTransitions,266  commitExitViewTransitions,267  commitBeforeUpdateViewTransition,268  commitNestedViewTransitions,269  restoreEnterOrExitViewTransitions,270  restoreUpdateViewTransition,271  restoreNestedViewTransitions,272  measureUpdateViewTransition,273  measureNestedViewTransitions,274  resetAppearingViewTransitions,275  trackAppearingViewTransition,276  viewTransitionCancelableChildren,277  pushViewTransitionCancelableScope,278  popViewTransitionCancelableScope,279} from './ReactFiberCommitViewTransitions';280import {281  viewTransitionMutationContext,282  pushRootMutationContext,283  pushMutationContext,284  popMutationContext,285  rootMutationContext,286} from './ReactFiberMutationTracking';287import {288  trackNamedViewTransition,289  untrackNamedViewTransition,290} from './ReactFiberDuplicateViewTransitions';291import {markIndicatorHandled} from './ReactFiberRootScheduler';292import type {Flags} from './ReactFiberFlags';293294type LayoutEffectTraversalFlags = number;295296const NoLayoutEffectTraversalFlags = /*        */ 0b00;297const IncludeWorkInProgressEffects = /*       */ 0b01;298const IncludeHostSingletons = /*              */ 0b10;299300// Used during the commit phase to track the state of the Offscreen component stack.301// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.302let offscreenSubtreeIsHidden: boolean = false;303let offscreenSubtreeWasHidden: boolean = false;304// Track whether there's a hidden offscreen above with no HostComponent between. If so,305// it overrides the hiddenness of the HostComponent below.306let offscreenDirectParentIsHidden: boolean = false;307308// Used to track if a form needs to be reset at the end of the mutation phase.309let needsFormReset = false;310311const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;312313let nextEffect: Fiber | null = null;314315// Used for Profiling builds to track updaters.316let inProgressLanes: Lanes | null = null;317let inProgressRoot: FiberRoot | null = null;318319let focusedInstanceHandle: null | Fiber = null;320export let shouldFireAfterActiveInstanceBlur: boolean = false;321322// Used during the commit phase to track whether a parent ViewTransition component323// might have been affected by any mutations / relayouts below.324let viewTransitionContextChanged: boolean = false;325let inUpdateViewTransition: boolean = false;326let rootViewTransitionAffected: boolean = false;327let rootViewTransitionNameCanceled: boolean = false;328329function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {330  if (finishedWork.tag === ActivityComponent) {331    const prevState: ActivityState | null = current.memoizedState;332    const nextState: ActivityState | null = finishedWork.memoizedState;333    return prevState !== null && nextState === null;334  } else if (finishedWork.tag === SuspenseComponent) {335    const prevState: SuspenseState | null = current.memoizedState;336    const nextState: SuspenseState | null = finishedWork.memoizedState;337    return (338      prevState !== null &&339      prevState.dehydrated !== null &&340      (nextState === null || nextState.dehydrated === null)341    );342  } else if (finishedWork.tag === HostRoot) {343    return (344      (current.memoizedState as RootState).isDehydrated &&345      (finishedWork.flags & ForceClientRender) === NoFlags346    );347  } else {348    return false;349  }350}351352export function commitBeforeMutationEffects(353  root: FiberRoot,354  firstChild: Fiber,355  committedLanes: Lanes,356): void {357  focusedInstanceHandle = prepareForCommit(root.containerInfo);358  shouldFireAfterActiveInstanceBlur = false;359360  const isViewTransitionEligible =361    enableViewTransition &&362    includesOnlyViewTransitionEligibleLanes(committedLanes);363364  nextEffect = firstChild;365  commitBeforeMutationEffects_begin(isViewTransitionEligible);366367  // We no longer need to track the active instance fiber368  focusedInstanceHandle = null;369  // We've found any matched pairs and can now reset.370  resetAppearingViewTransitions();371}372373function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {374  // If this commit is eligible for a View Transition we look into all mutated subtrees.375  // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.376  const subtreeMask = isViewTransitionEligible377    ? BeforeAndAfterMutationTransitionMask378    : BeforeMutationMask;379  while (nextEffect !== null) {380    const fiber = nextEffect;381382    // This phase is only used for beforeActiveInstanceBlur.383    // Let's skip the whole loop if it's off.384    if (enableCreateEventHandleAPI || isViewTransitionEligible) {385      // TODO: Should wrap this in flags check, too, as optimization386      const deletions = fiber.deletions;387      if (deletions !== null) {388        for (let i = 0; i < deletions.length; i++) {389          const deletion = deletions[i];390          commitBeforeMutationEffectsDeletion(391            deletion,392            isViewTransitionEligible,393          );394        }395      }396    }397398    if (399      enableViewTransition &&400      fiber.alternate === null &&401      (fiber.flags & Placement) !== NoFlags402    ) {403      // Skip before mutation effects of the children because we don't want404      // to trigger updates of any nested view transitions and we shouldn't405      // have any other before mutation effects since snapshot effects are406      // only applied to updates. TODO: Model this using only flags.407      if (isViewTransitionEligible) {408        trackEnterViewTransitions(fiber);409      }410      commitBeforeMutationEffects_complete(isViewTransitionEligible);411      continue;412    }413414    // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.415    if (enableViewTransition && fiber.tag === OffscreenComponent) {416      const isModernRoot =417        disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;418      if (isModernRoot) {419        const current = fiber.alternate;420        const isHidden = fiber.memoizedState !== null;421        if (isHidden) {422          if (423            current !== null &&424            current.memoizedState === null &&425            isViewTransitionEligible426          ) {427            // Was previously mounted as visible but is now hidden.428            commitExitViewTransitions(current);429          }430          // Skip before mutation effects of the children because they're hidden.431          commitBeforeMutationEffects_complete(isViewTransitionEligible);432          continue;433        } else if (current !== null && current.memoizedState !== null) {434          // Was previously mounted as hidden but is now visible.435          // Skip before mutation effects of the children because we don't want436          // to trigger updates of any nested view transitions and we shouldn't437          // have any other before mutation effects since snapshot effects are438          // only applied to updates. TODO: Model this using only flags.439          if (isViewTransitionEligible) {440            trackEnterViewTransitions(fiber);441          }442          commitBeforeMutationEffects_complete(isViewTransitionEligible);443          continue;444        }445      }446    }447448    const child = fiber.child;449    if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {450      child.return = fiber;451      nextEffect = child;452    } else {453      if (isViewTransitionEligible) {454        // We are inside an updated subtree. Any mutations that affected the455        // parent HostInstance's layout or set of children (such as reorders)456        // might have also affected the positioning or size of the inner457        // ViewTransitions. Therefore we need to find them inside.458        commitNestedViewTransitions(fiber);459      }460      commitBeforeMutationEffects_complete(isViewTransitionEligible);461    }462  }463}464465function commitBeforeMutationEffects_complete(466  isViewTransitionEligible: boolean,467) {468  while (nextEffect !== null) {469    const fiber = nextEffect;470    commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);471472    const sibling = fiber.sibling;473    if (sibling !== null) {474      sibling.return = fiber.return;475      nextEffect = sibling;476      return;477    }478479    nextEffect = fiber.return;480  }481}482483function commitBeforeMutationEffectsOnFiber(484  finishedWork: Fiber,485  isViewTransitionEligible: boolean,486) {487  const current = finishedWork.alternate;488  const flags = finishedWork.flags;489490  if (enableCreateEventHandleAPI) {491    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {492      // Check to see if the focused element was inside of a hidden (Suspense) subtree.493      // TODO: Move this out of the hot path using a dedicated effect tag.494      // TODO: This should consider Offscreen in general and not just SuspenseComponent.495      if (496        finishedWork.tag === SuspenseComponent &&497        isSuspenseBoundaryBeingHidden(current, finishedWork) &&498        // $FlowFixMe[incompatible-type] found when upgrading Flow499        doesFiberContain(finishedWork, focusedInstanceHandle)500      ) {501        shouldFireAfterActiveInstanceBlur = true;502        beforeActiveInstanceBlur(finishedWork);503      }504    }505  }506507  switch (finishedWork.tag) {508    case FunctionComponent:509    case ForwardRef:510    case SimpleMemoComponent: {511      if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {512        const updateQueue: FunctionComponentUpdateQueue | null =513          finishedWork.updateQueue as any;514        const eventPayloads = updateQueue !== null ? updateQueue.events : null;515        if (eventPayloads !== null) {516          for (let ii = 0; ii < eventPayloads.length; ii++) {517            const {ref, nextImpl} = eventPayloads[ii];518            ref.impl = nextImpl;519          }520        }521      }522      break;523    }524    case ClassComponent: {525      if ((flags & Snapshot) !== NoFlags) {526        if (current !== null) {527          commitClassSnapshot(finishedWork, current);528        }529      }530      break;531    }532    case HostRoot: {533      if ((flags & Snapshot) !== NoFlags) {534        // $FlowFixMe[constant-condition]535        if (supportsMutation) {536          const root = finishedWork.stateNode;537          clearContainer(root.containerInfo);538        }539      }540      break;541    }542    case HostComponent:543    case HostHoistable:544    case HostSingleton:545    case HostText:546    case HostPortal:547    case IncompleteClassComponent:548      // Nothing to do for these component types549      break;550    case ViewTransitionComponent:551      if (enableViewTransition) {552        if (isViewTransitionEligible) {553          if (current === null) {554            // This is a new mount. We should have handled this as part of the555            // Placement effect or it is deeper inside a entering transition.556          } else {557            // Something may have mutated within this subtree. This might need to cause558            // a cross-fade of this parent. We first assign old names to the559            // previous tree in the before mutation phase in case we need to.560            // TODO: This walks the tree that we might continue walking anyway.561            // We should just stash the parent ViewTransitionComponent and continue562            // walking the tree until we find HostComponent but to do that we need563            // to use a stack which requires refactoring this phase.564            commitBeforeUpdateViewTransition(current, finishedWork);565          }566        }567        break;568      }569    // Fallthrough570    default: {571      if ((flags & Snapshot) !== NoFlags) {572        throw new Error(573          'This unit of work tag should not have side-effects. This error is ' +574            'likely caused by a bug in React. Please file an issue.',575        );576      }577    }578  }579}580581function commitBeforeMutationEffectsDeletion(582  deletion: Fiber,583  isViewTransitionEligible: boolean,584) {585  if (enableCreateEventHandleAPI) {586    // TODO (effects) It would be nice to avoid calling doesFiberContain()587    // Maybe we can repurpose one of the subtreeFlags positions for this instead?588    // Use it to store which part of the tree the focused instance is in?589    // This assumes we can safely determine that instance during the "render" phase.590    if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {591      shouldFireAfterActiveInstanceBlur = true;592      beforeActiveInstanceBlur(deletion);593    }594  }595  if (isViewTransitionEligible) {596    commitExitViewTransitions(deletion);597  }598}599600function commitLayoutEffectOnFiber(601  finishedRoot: FiberRoot,602  current: Fiber | null,603  finishedWork: Fiber,604  committedLanes: Lanes,605): void {606  const prevEffectStart = pushComponentEffectStart();607  const prevEffectDuration = pushComponentEffectDuration();608  const prevEffectErrors = pushComponentEffectErrors();609  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();610  // When updating this function, also update reappearLayoutEffects, which does611  // most of the same things when an offscreen tree goes from hidden -> visible.612  const flags = finishedWork.flags;613  switch (finishedWork.tag) {614    case FunctionComponent:615    case ForwardRef:616    case SimpleMemoComponent: {617      recursivelyTraverseLayoutEffects(618        finishedRoot,619        finishedWork,620        committedLanes,621      );622      if (flags & Update) {623        commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);624      }625      break;626    }627    case ClassComponent: {628      recursivelyTraverseLayoutEffects(629        finishedRoot,630        finishedWork,631        committedLanes,632      );633      if (flags & Update) {634        commitClassLayoutLifecycles(finishedWork, current);635      }636637      if (flags & Callback) {638        commitClassCallbacks(finishedWork);639      }640641      if (flags & Ref) {642        safelyAttachRef(finishedWork, finishedWork.return);643      }644      break;645    }646    case HostRoot: {647      const prevProfilerEffectDuration = pushNestedEffectDurations();648      recursivelyTraverseLayoutEffects(649        finishedRoot,650        finishedWork,651        committedLanes,652      );653      if (flags & Callback) {654        commitRootCallbacks(finishedWork);655      }656      if (enableProfilerTimer && enableProfilerCommitHooks) {657        finishedRoot.effectDuration += popNestedEffectDurations(658          prevProfilerEffectDuration,659        );660      }661      break;662    }663    case HostSingleton: {664      // $FlowFixMe[constant-condition]665      if (supportsSingletons) {666        // We acquire the singleton instance first so it has appropriate667        // styles before other layout effects run. This isn't perfect because668        // an early sibling of the singleton may have an effect that can669        // observe the singleton before it is acquired.670        // @TODO move this to the mutation phase. The reason it isn't there yet671        // is it seemingly requires an extra traversal because we need to move the672        // disappear effect into a phase before the appear phase673        if (current === null && flags & Update) {674          // Unlike in the reappear path we only acquire on new mount675          commitHostSingletonAcquisition(finishedWork);676        }677        // We fall through to the HostComponent case below.678      }679      // Fallthrough680    }681    case HostHoistable:682    case HostComponent: {683      recursivelyTraverseLayoutEffects(684        finishedRoot,685        finishedWork,686        committedLanes,687      );688689      // Renderers may schedule work to be done after host components are mounted690      // (eg DOM renderer may schedule auto-focus for inputs and form controls).691      // These effects should only be committed when components are first mounted,692      // aka when there is no current/alternate.693      if (current === null) {694        if (flags & Update) {695          commitHostMount(finishedWork);696        } else if (flags & Hydrate) {697          commitHostHydratedInstance(finishedWork);698        }699      }700701      if (flags & Ref) {702        safelyAttachRef(finishedWork, finishedWork.return);703      }704      break;705    }706    case Profiler: {707      // TODO: Should this fire inside an offscreen tree? Or should it wait to708      // fire when the tree becomes visible again.709      if (flags & Update) {710        const prevProfilerEffectDuration = pushNestedEffectDurations();711712        recursivelyTraverseLayoutEffects(713          finishedRoot,714          finishedWork,715          committedLanes,716        );717718        const profilerInstance = finishedWork.stateNode;719720        if (enableProfilerTimer && enableProfilerCommitHooks) {721          // Propagate layout effect durations to the next nearest Profiler ancestor.722          // Do not reset these values until the next render so DevTools has a chance to read them first.723          profilerInstance.effectDuration += bubbleNestedEffectDurations(724            prevProfilerEffectDuration,725          );726        }727728        commitProfilerUpdate(729          finishedWork,730          current,731          commitStartTime,732          profilerInstance.effectDuration,733        );734      } else {735        recursivelyTraverseLayoutEffects(736          finishedRoot,737          finishedWork,738          committedLanes,739        );740      }741      break;742    }743    case ActivityComponent: {744      recursivelyTraverseLayoutEffects(745        finishedRoot,746        finishedWork,747        committedLanes,748      );749      if (flags & Update) {750        commitActivityHydrationCallbacks(finishedRoot, finishedWork);751      }752      break;753    }754    case SuspenseComponent: {755      recursivelyTraverseLayoutEffects(756        finishedRoot,757        finishedWork,758        committedLanes,759      );760      if (flags & Update) {761        commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);762      }763      if (flags & Callback) {764        // This Boundary is in fallback and has a dehydrated Suspense instance.765        // We could in theory assume the dehydrated state but we recheck it for766        // certainty.767        const finishedState: SuspenseState | null = finishedWork.memoizedState;768        if (finishedState !== null) {769          const dehydrated = finishedState.dehydrated;770          if (dehydrated !== null) {771            // Register a callback to retry this boundary once the server has sent the result.772            const retry = retryDehydratedSuspenseBoundary.bind(773              null,774              finishedWork,775            );776            registerSuspenseInstanceRetry(dehydrated, retry);777          }778        }779      }780      break;781    }782    case OffscreenComponent: {783      const isModernRoot =784        disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;785      if (isModernRoot) {786        const isHidden = finishedWork.memoizedState !== null;787        const newOffscreenSubtreeIsHidden =788          isHidden || offscreenSubtreeIsHidden;789        if (newOffscreenSubtreeIsHidden) {790          // The Offscreen tree is hidden. Skip over its layout effects.791        } else {792          // The Offscreen tree is visible.793794          const wasHidden = current !== null && current.memoizedState !== null;795          const newOffscreenSubtreeWasHidden =796            wasHidden || offscreenSubtreeWasHidden;797          const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;798          const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;799          offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;800          offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;801802          if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {803            // This is the root of a reappearing boundary. As we continue804            // traversing the layout effects, we must also re-mount layout805            // effects that were unmounted when the Offscreen subtree was806            // hidden. So this is a superset of the normal commitLayoutEffects.807            let layoutEffectTraversalFlags: LayoutEffectTraversalFlags;808            // $FlowFixMe[constant-condition]809            if (supportsSingletons) {810              layoutEffectTraversalFlags = IncludeHostSingletons;811            } else {812              layoutEffectTraversalFlags = NoLayoutEffectTraversalFlags;813            }814            if ((finishedWork.subtreeFlags & LayoutMask) !== NoFlags) {815              layoutEffectTraversalFlags |= IncludeWorkInProgressEffects;816            }817            recursivelyTraverseReappearLayoutEffects(818              finishedRoot,819              finishedWork,820              layoutEffectTraversalFlags,821            );822            if (823              enableProfilerTimer &&824              enableProfilerCommitHooks &&825              enableComponentPerformanceTrack &&826              (finishedWork.mode & ProfileMode) !== NoMode &&827              componentEffectStartTime >= 0 &&828              componentEffectEndTime >= 0 &&829              componentEffectEndTime - componentEffectStartTime > 0.05830            ) {831              logComponentReappeared(832                finishedWork,833                componentEffectStartTime,834                componentEffectEndTime,835              );836            }837          } else {838            recursivelyTraverseLayoutEffects(839              finishedRoot,840              finishedWork,841              committedLanes,842            );843          }844          offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;845          offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;846        }847      } else {848        recursivelyTraverseLayoutEffects(849          finishedRoot,850          finishedWork,851          committedLanes,852        );853      }854      break;855    }856    case ViewTransitionComponent: {857      if (enableViewTransition) {858        if (__DEV__) {859          if (flags & ViewTransitionNamedStatic) {860            trackNamedViewTransition(finishedWork);861          }862        }863        recursivelyTraverseLayoutEffects(864          finishedRoot,865          finishedWork,866          committedLanes,867        );868        if (flags & Ref) {869          safelyAttachRef(finishedWork, finishedWork.return);870        }871        break;872      }873      break;874    }875    case Fragment:876      if (enableFragmentRefs) {877        if (flags & Ref) {878          safelyAttachRef(finishedWork, finishedWork.return);879        }880      }881    // Fallthrough882    default: {883      recursivelyTraverseLayoutEffects(884        finishedRoot,885        finishedWork,886        committedLanes,887      );888      break;889    }890  }891892  if (893    enableProfilerTimer &&894    enableProfilerCommitHooks &&895    enableComponentPerformanceTrack &&896    (finishedWork.mode & ProfileMode) !== NoMode &&897    componentEffectStartTime >= 0 &&898    componentEffectEndTime >= 0899  ) {900    if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {901      logComponentEffect(902        finishedWork,903        componentEffectStartTime,904        componentEffectEndTime,905        componentEffectDuration,906        componentEffectErrors,907      );908    }909    if (910      // Insertion911      finishedWork.alternate === null &&912      finishedWork.return !== null &&913      finishedWork.return.alternate !== null &&914      componentEffectEndTime - componentEffectStartTime > 0.05915    ) {916      const isHydration = isHydratingParent(917        finishedWork.return.alternate,918        finishedWork.return,919      );920      if (!isHydration) {921        logComponentMount(922          finishedWork,923          componentEffectStartTime,924          componentEffectEndTime,925        );926      }927    }928  }929930  popComponentEffectStart(prevEffectStart);931  popComponentEffectDuration(prevEffectDuration);932  popComponentEffectErrors(prevEffectErrors);933  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);934}935936function abortRootTransitions(937  root: FiberRoot,938  abort: TransitionAbort,939  deletedTransitions: Set<Transition>,940  deletedOffscreenInstance: OffscreenInstance | null,941  isInDeletedTree: boolean,942) {943  if (enableTransitionTracing) {944    const rootTransitions = root.incompleteTransitions;945    deletedTransitions.forEach(transition => {946      if (rootTransitions.has(transition)) {947        const transitionInstance: TracingMarkerInstance = rootTransitions.get(948          transition,949        ) as any;950        if (transitionInstance.aborts === null) {951          transitionInstance.aborts = [];952        }953        transitionInstance.aborts.push(abort);954955        if (deletedOffscreenInstance !== null) {956          if (957            transitionInstance.pendingBoundaries !== null &&958            transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)959          ) {960            // $FlowFixMe[incompatible-use] found when upgrading Flow961            transitionInstance.pendingBoundaries.delete(962              deletedOffscreenInstance,963            );964          }965        }966      }967    });968  }969}970971function abortTracingMarkerTransitions(972  abortedFiber: Fiber,973  abort: TransitionAbort,974  deletedTransitions: Set<Transition>,975  deletedOffscreenInstance: OffscreenInstance | null,976  isInDeletedTree: boolean,977) {978  if (enableTransitionTracing) {979    const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;980    const markerTransitions = markerInstance.transitions;981    const pendingBoundaries = markerInstance.pendingBoundaries;982    if (markerTransitions !== null) {983      // TODO: Refactor this code. Is there a way to move this code to984      // the deletions phase instead of calculating it here while making sure985      // complete is called appropriately?986      deletedTransitions.forEach(transition => {987        // If one of the transitions on the tracing marker is a transition988        // that was in an aborted subtree, we will abort that tracing marker989        if (990          // $FlowFixMe[invalid-compare]991          abortedFiber !== null &&992          markerTransitions.has(transition) &&993          (markerInstance.aborts === null ||994            !markerInstance.aborts.includes(abort))995        ) {996          if (markerInstance.transitions !== null) {997            if (markerInstance.aborts === null) {998              markerInstance.aborts = [abort];999              addMarkerIncompleteCallbackToPendingTransition(1000                abortedFiber.memoizedProps.name,1001                markerInstance.transitions,1002                markerInstance.aborts,1003              );1004            } else {1005              markerInstance.aborts.push(abort);1006            }10071008            // We only want to call onTransitionProgress when the marker hasn't been1009            // deleted1010            if (1011              deletedOffscreenInstance !== null &&1012              !isInDeletedTree &&1013              pendingBoundaries !== null &&1014              pendingBoundaries.has(deletedOffscreenInstance)1015            ) {1016              pendingBoundaries.delete(deletedOffscreenInstance);10171018              addMarkerProgressCallbackToPendingTransition(1019                abortedFiber.memoizedProps.name,1020                deletedTransitions,1021                pendingBoundaries,1022              );1023            }1024          }1025        }1026      });1027    }1028  }1029}10301031function abortParentMarkerTransitionsForDeletedFiber(1032  abortedFiber: Fiber,1033  abort: TransitionAbort,1034  deletedTransitions: Set<Transition>,1035  deletedOffscreenInstance: OffscreenInstance | null,1036  isInDeletedTree: boolean,1037) {1038  if (enableTransitionTracing) {1039    // Find all pending markers that are waiting on child suspense boundaries in the1040    // aborted subtree and cancels them1041    let fiber: null | Fiber = abortedFiber;1042    while (fiber !== null) {1043      switch (fiber.tag) {1044        case TracingMarkerComponent:1045          abortTracingMarkerTransitions(1046            fiber,1047            abort,1048            deletedTransitions,1049            deletedOffscreenInstance,1050            isInDeletedTree,1051          );1052          break;1053        case HostRoot:1054          const root = fiber.stateNode;1055          abortRootTransitions(1056            root,1057            abort,1058            deletedTransitions,1059            deletedOffscreenInstance,1060            isInDeletedTree,1061          );10621063          break;1064        default:1065          break;1066      }10671068      fiber = fiber.return;1069    }1070  }1071}10721073function commitTransitionProgress(offscreenFiber: Fiber) {1074  if (enableTransitionTracing) {1075    // This function adds suspense boundaries to the root1076    // or tracing marker's pendingBoundaries map.1077    // When a suspense boundary goes from a resolved to a fallback1078    // state we add the boundary to the map, and when it goes from1079    // a fallback to a resolved state, we remove the boundary from1080    // the map.10811082    // We use stateNode on the Offscreen component as a stable object1083    // that doesnt change from render to render. This way we can1084    // distinguish between different Offscreen instances (vs. the same1085    // Offscreen instance with different fibers)1086    const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10871088    let prevState: SuspenseState | null = null;1089    const previousFiber = offscreenFiber.alternate;1090    if (previousFiber !== null && previousFiber.memoizedState !== null) {1091      prevState = previousFiber.memoizedState;1092    }1093    const nextState: SuspenseState | null = offscreenFiber.memoizedState;10941095    const wasHidden = prevState !== null;1096    const isHidden = nextState !== null;10971098    const pendingMarkers = offscreenInstance._pendingMarkers;1099    // If there is a name on the suspense boundary, store that in1100    // the pending boundaries.1101    let name = null;1102    const parent = offscreenFiber.return;1103    if (1104      parent !== null &&1105      parent.tag === SuspenseComponent &&1106      parent.memoizedProps.name1107    ) {1108      name = parent.memoizedProps.name;1109    }11101111    if (!wasHidden && isHidden) {1112      // The suspense boundaries was just hidden. Add the boundary1113      // to the pending boundary set if it's there1114      if (pendingMarkers !== null) {1115        pendingMarkers.forEach(markerInstance => {1116          const pendingBoundaries = markerInstance.pendingBoundaries;1117          const transitions = markerInstance.transitions;1118          const markerName = markerInstance.name;1119          if (1120            pendingBoundaries !== null &&1121            !pendingBoundaries.has(offscreenInstance)1122          ) {1123            pendingBoundaries.set(offscreenInstance, {1124              name,1125            });1126            if (transitions !== null) {1127              if (1128                markerInstance.tag === TransitionTracingMarker &&1129                markerName !== null1130              ) {1131                addMarkerProgressCallbackToPendingTransition(1132                  markerName,1133                  transitions,1134                  pendingBoundaries,1135                );1136              } else if (markerInstance.tag === TransitionRoot) {1137                transitions.forEach(transition => {1138                  addTransitionProgressCallbackToPendingTransition(1139                    transition,1140                    pendingBoundaries,1141                  );1142                });1143              }1144            }1145          }1146        });1147      }1148    } else if (wasHidden && !isHidden) {1149      // The suspense boundary went from hidden to visible. Remove1150      // the boundary from the pending suspense boundaries set1151      // if it's there1152      if (pendingMarkers !== null) {1153        pendingMarkers.forEach(markerInstance => {1154          const pendingBoundaries = markerInstance.pendingBoundaries;1155          const transitions = markerInstance.transitions;1156          const markerName = markerInstance.name;1157          if (1158            pendingBoundaries !== null &&1159            pendingBoundaries.has(offscreenInstance)1160          ) {1161            pendingBoundaries.delete(offscreenInstance);1162            if (transitions !== null) {1163              if (1164                markerInstance.tag === TransitionTracingMarker &&1165                markerName !== null1166              ) {1167                addMarkerProgressCallbackToPendingTransition(1168                  markerName,1169                  transitions,1170                  pendingBoundaries,1171                );11721173                // If there are no more unresolved suspense boundaries, the interaction1174                // is considered finished1175                if (pendingBoundaries.size === 0) {1176                  if (markerInstance.aborts === null) {1177                    addMarkerCompleteCallbackToPendingTransition(1178                      markerName,1179                      transitions,1180                    );1181                  }1182                  markerInstance.transitions = null;1183                  markerInstance.pendingBoundaries = null;1184                  markerInstance.aborts = null;1185                }1186              } else if (markerInstance.tag === TransitionRoot) {1187                transitions.forEach(transition => {1188                  addTransitionProgressCallbackToPendingTransition(1189                    transition,1190                    pendingBoundaries,1191                  );1192                });1193              }1194            }1195          }1196        });1197      }1198    }1199  }1200}12011202function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {1203  // $FlowFixMe[constant-condition]1204  if (!supportsMutation) {1205    return;1206  }1207  // Finds the nearest host component children and updates their visibility1208  // to either hidden or visible.1209  let child = parentFiber.child;1210  while (child !== null) {1211    hideOrUnhideAllChildrenOnFiber(child, isHidden);1212    child = child.sibling;1213  }1214}12151216function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {1217  // $FlowFixMe[constant-condition]1218  if (!supportsMutation) {1219    return;1220  }1221  switch (fiber.tag) {1222    case HostComponent:1223    case HostHoistable: {1224      // Found the nearest host component. Hide it.1225      commitShowHideHostInstance(fiber, isHidden);1226      // Typically, only the nearest host nodes need to be hidden, since that1227      // has the effect of also hiding everything inside of them.1228      //1229      // However, there's a special case for portals, because portals do not1230      // exist in the regular host tree hierarchy; we can't assume that just1231      // because a portal's HostComponent parent in the React tree will also be1232      // a parent in the actual host tree.1233      //1234      // So, if any portals exist within the tree, regardless of how deeply1235      // nested they are, we need to repeat this algorithm for its children.1236      hideOrUnhideNearestPortals(fiber, isHidden);1237      return;1238    }1239    case HostText: {1240      commitShowHideHostTextInstance(fiber, isHidden);1241      return;1242    }1243    case DehydratedFragment: {1244      commitShowHideSuspenseBoundary(fiber, isHidden);1245      return;1246    }1247    case OffscreenComponent:1248    case LegacyHiddenComponent: {1249      const offscreenState: OffscreenState | null = fiber.memoizedState;1250      if (offscreenState !== null) {1251        // Found a nested Offscreen component that is hidden.1252        // Don't search any deeper. This tree should remain hidden.1253      } else {1254        hideOrUnhideAllChildren(fiber, isHidden);1255      }1256      return;1257    }1258    default: {1259      hideOrUnhideAllChildren(fiber, isHidden);1260      return;1261    }1262  }1263}12641265function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {1266  // $FlowFixMe[constant-condition]1267  if (!supportsMutation) {1268    return;1269  }1270  if (parentFiber.subtreeFlags & PortalStatic) {1271    let child = parentFiber.child;1272    while (child !== null) {1273      hideOrUnhideNearestPortalsOnFiber(child, isHidden);1274      child = child.sibling;1275    }1276  }1277}12781279function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {1280  // $FlowFixMe[constant-condition]1281  if (!supportsMutation) {1282    return;1283  }1284  switch (fiber.tag) {1285    case HostPortal: {1286      // Found a portal. Switch back to the normal hide/unhide algorithm to1287      // toggle the visibility of its children.1288      hideOrUnhideAllChildrenOnFiber(fiber, isHidden);1289      return;1290    }1291    case OffscreenComponent: {1292      const offscreenState: OffscreenState | null = fiber.memoizedState;1293      if (offscreenState !== null) {1294        // Found a nested Offscreen component that is hidden. Don't search any1295        // deeper. This tree should remain hidden.1296      } else {1297        hideOrUnhideNearestPortals(fiber, isHidden);1298      }1299      return;1300    }1301    default: {1302      hideOrUnhideNearestPortals(fiber, isHidden);1303      return;1304    }1305  }1306}13071308function detachFiberMutation(fiber: Fiber) {1309  // Cut off the return pointer to disconnect it from the tree.1310  // This enables us to detect and warn against state updates on an unmounted component.1311  // It also prevents events from bubbling from within disconnected components.1312  //1313  // Ideally, we should also clear the child pointer of the parent alternate to let this1314  // get GC:ed but we don't know which for sure which parent is the current1315  // one so we'll settle for GC:ing the subtree of this child.1316  // This child itself will be GC:ed when the parent updates the next time.1317  //1318  // Note that we can't clear child or sibling pointers yet.1319  // They're needed for passive effects and for findDOMNode.1320  // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).1321  //1322  // Don't reset the alternate yet, either. We need that so we can detach the1323  // alternate's fields in the passive phase. Clearing the return pointer is1324  // sufficient for findDOMNode semantics.1325  const alternate = fiber.alternate;1326  if (alternate !== null) {1327    alternate.return = null;1328  }1329  fiber.return = null;1330}13311332function detachFiberAfterEffects(fiber: Fiber) {1333  const alternate = fiber.alternate;1334  if (alternate !== null) {1335    fiber.alternate = null;1336    detachFiberAfterEffects(alternate);1337  }13381339  // Clear cyclical Fiber fields. This level alone is designed to roughly1340  // approximate the planned Fiber refactor. In that world, `setState` will be1341  // bound to a special "instance" object instead of a Fiber. The Instance1342  // object will not have any of these fields. It will only be connected to1343  // the fiber tree via a single link at the root. So if this level alone is1344  // sufficient to fix memory issues, that bodes well for our plans.1345  fiber.child = null;1346  fiber.deletions = null;1347  fiber.sibling = null;13481349  // The `stateNode` is cyclical because on host nodes it points to the host1350  // tree, which has its own pointers to children, parents, and siblings.1351  // The other host nodes also point back to fibers, so we should detach that1352  // one, too.1353  if (fiber.tag === HostComponent) {1354    const hostInstance: Instance = fiber.stateNode;1355    // $FlowFixMe[invalid-compare]1356    if (hostInstance !== null) {1357      detachDeletedInstance(hostInstance);1358    }1359  }1360  fiber.stateNode = null;13611362  if (__DEV__) {1363    fiber._debugOwner = null;1364  }13651366  // Theoretically, nothing in here should be necessary, because we already1367  // disconnected the fiber from the tree. So even if something leaks this1368  // particular fiber, it won't leak anything else.1369  fiber.return = null;1370  fiber.dependencies = null;1371  fiber.memoizedProps = null;1372  fiber.memoizedState = null;1373  fiber.pendingProps = null;1374  fiber.stateNode = null;1375  // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.1376  fiber.updateQueue = null;1377}13781379// These are tracked on the stack as we recursively traverse a1380// deleted subtree.1381// TODO: Update these during the whole mutation phase, not just during1382// a deletion.1383let hostParent: Instance | Container | null = null;1384let hostParentIsContainer: boolean = false;13851386function commitDeletionEffects(1387  root: FiberRoot,1388  returnFiber: Fiber,1389  deletedFiber: Fiber,1390) {1391  const prevEffectStart = pushComponentEffectStart();13921393  // $FlowFixMe[constant-condition]1394  if (supportsMutation) {1395    // We only have the top Fiber that was deleted but we need to recurse down its1396    // children to find all the terminal nodes.13971398    // Recursively delete all host nodes from the parent, detach refs, clean1399    // up mounted layout effects, and call componentWillUnmount.14001401    // We only need to remove the topmost host child in each branch. But then we1402    // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1403    // could split this into two separate traversals functions, where the second1404    // one doesn't include any removeChild logic. This is maybe the same1405    // function as "disappearLayoutEffects" (or whatever that turns into after1406    // the layout phase is refactored to use recursion).14071408    // Before starting, find the nearest host parent on the stack so we know1409    // which instance/container to remove the children from.1410    // TODO: Instead of searching up the fiber return path on every deletion, we1411    // can track the nearest host component on the JS stack as we traverse the1412    // tree during the commit phase. This would make insertions faster, too.1413    let parent: null | Fiber = returnFiber;1414    findParent: while (parent !== null) {1415      switch (parent.tag) {1416        case HostSingleton: {1417          // $FlowFixMe[constant-condition]1418          if (supportsSingletons) {1419            if (isSingletonScope(parent.type)) {1420              hostParent = parent.stateNode;1421              hostParentIsContainer = false;1422              break findParent;1423            }1424            break;1425          }1426          // Expected fallthrough when supportsSingletons is false1427        }1428        case HostComponent: {1429          hostParent = parent.stateNode;1430          hostParentIsContainer = false;1431          break findParent;1432        }1433        case HostRoot:1434        case HostPortal: {1435          hostParent = parent.stateNode.containerInfo;1436          hostParentIsContainer = true;1437          break findParent;1438        }1439      }1440      parent = parent.return;1441    }1442    if (hostParent === null) {1443      throw new Error(1444        'Expected to find a host parent. This error is likely caused by ' +1445          'a bug in React. Please file an issue.',1446      );1447    }14481449    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1450    hostParent = null;1451    hostParentIsContainer = false;1452  } else {1453    // Detach refs and call componentWillUnmount() on the whole subtree.1454    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1455  }14561457  if (1458    enableProfilerTimer &&1459    enableProfilerCommitHooks &&1460    enableComponentPerformanceTrack &&1461    (deletedFiber.mode & ProfileMode) !== NoMode &&1462    componentEffectStartTime >= 0 &&1463    componentEffectEndTime >= 0 &&1464    componentEffectEndTime - componentEffectStartTime > 0.051465  ) {1466    logComponentUnmount(1467      deletedFiber,1468      componentEffectStartTime,1469      componentEffectEndTime,1470    );1471  }1472  popComponentEffectStart(prevEffectStart);14731474  detachFiberMutation(deletedFiber);1475}14761477function recursivelyTraverseDeletionEffects(1478  finishedRoot: FiberRoot,1479  nearestMountedAncestor: Fiber,1480  parent: Fiber,1481) {1482  // TODO: Use a static flag to skip trees that don't have unmount effects1483  let child = parent.child;1484  while (child !== null) {1485    commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1486    child = child.sibling;1487  }1488}14891490function commitDeletionEffectsOnFiber(1491  finishedRoot: FiberRoot,1492  nearestMountedAncestor: Fiber,1493  deletedFiber: Fiber,1494) {1495  // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.1496  onCommitUnmount(deletedFiber);14971498  const prevEffectStart = pushComponentEffectStart();1499  const prevEffectDuration = pushComponentEffectDuration();1500  const prevEffectErrors = pushComponentEffectErrors();1501  const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();15021503  // The cases in this outer switch modify the stack before they traverse1504  // into their subtree. There are simpler cases in the inner switch1505  // that don't modify the stack.1506  switch (deletedFiber.tag) {1507    case HostHoistable: {1508      // $FlowFixMe[constant-condition]1509      if (supportsResources) {1510        if (!offscreenSubtreeWasHidden) {1511          safelyDetachRef(deletedFiber, nearestMountedAncestor);1512        }1513        recursivelyTraverseDeletionEffects(1514          finishedRoot,1515          nearestMountedAncestor,1516          deletedFiber,1517        );1518        if (deletedFiber.memoizedState) {1519          releaseResource(deletedFiber.memoizedState);1520        } else if (deletedFiber.stateNode) {1521          // A Hoistable Instance lives in document.head only when its enclosing1522          // Activity is visible. If the Activity is hidden (or has been hidden1523          // since mount), the instance was either never inserted or was1524          // detached by the disappear traversal. Skip in those cases.1525          if (!offscreenSubtreeWasHidden) {1526            unmountHoistable(deletedFiber.stateNode);1527          }1528        }1529        break;1530      }1531      // Fall through1532    }1533    case HostSingleton: {1534      // $FlowFixMe[constant-condition]1535      if (supportsSingletons) {1536        if (!offscreenSubtreeWasHidden) {1537          safelyDetachRef(deletedFiber, nearestMountedAncestor);1538        }1539        if (enableFragmentRefs) {1540          commitFragmentInstanceDeletionEffects(deletedFiber);1541        }15421543        const prevHostParent = hostParent;1544        const prevHostParentIsContainer = hostParentIsContainer;1545        if (isSingletonScope(deletedFiber.type)) {1546          hostParent = deletedFiber.stateNode;1547          hostParentIsContainer = false;1548        }1549        recursivelyTraverseDeletionEffects(1550          finishedRoot,1551          nearestMountedAncestor,1552          deletedFiber,1553        );15541555        // Normally this is called in passive unmount effect phase however with1556        // HostSingleton we warn if you acquire one that is already associated to1557        // a different fiber. To increase our chances of avoiding this, specifically1558        // if you keyed a HostSingleton so there will be a delete followed by a Placement1559        // we treat detach eagerly here1560        commitHostSingletonRelease(deletedFiber);15611562        hostParent = prevHostParent;1563        hostParentIsContainer = prevHostParentIsContainer;15641565        break;1566      }1567      // Fall through1568    }1569    case HostComponent: {1570      if (!offscreenSubtreeWasHidden) {1571        safelyDetachRef(deletedFiber, nearestMountedAncestor);1572      }1573      if (enableFragmentRefs) {1574        commitFragmentInstanceDeletionEffects(deletedFiber);1575      }1576      // Intentional fallthrough to next branch1577    }1578    case HostText: {1579      if (1580        enableFragmentRefs &&1581        enableFragmentRefsTextNodes &&1582        // HostComponent falls through into this case.1583        deletedFiber.tag === HostText1584      ) {1585        commitFragmentInstanceDeletionEffects(deletedFiber);1586      }1587      // We only need to remove the nearest host child. Set the host parent1588      // to `null` on the stack to indicate that nested children don't1589      // need to be removed.1590      // $FlowFixMe[constant-condition]1591      if (supportsMutation) {1592        const prevHostParent = hostParent;1593        const prevHostParentIsContainer = hostParentIsContainer;1594        hostParent = null;1595        recursivelyTraverseDeletionEffects(1596          finishedRoot,1597          nearestMountedAncestor,1598          deletedFiber,1599        );1600        hostParent = prevHostParent;1601        hostParentIsContainer = prevHostParentIsContainer;16021603        if (hostParent !== null) {1604          // Now that all the child effects have unmounted, we can remove the1605          // node from the tree.1606          if (hostParentIsContainer) {1607            commitHostRemoveChildFromContainer(1608              deletedFiber,1609              nearestMountedAncestor,1610              hostParent as any as Container,1611              deletedFiber.stateNode as Instance | TextInstance,1612            );1613          } else {1614            commitHostRemoveChild(1615              deletedFiber,1616              nearestMountedAncestor,1617              hostParent as any as Instance,1618              deletedFiber.stateNode as Instance | TextInstance,1619            );1620          }1621        }1622      } else {1623        recursivelyTraverseDeletionEffects(1624          finishedRoot,1625          nearestMountedAncestor,1626          deletedFiber,1627        );1628      }1629      break;1630    }1631    case DehydratedFragment: {1632      if (enableSuspenseCallback) {1633        const hydrationCallbacks = finishedRoot.hydrationCallbacks;1634        if (hydrationCallbacks !== null) {1635          try {1636            const onDeleted = hydrationCallbacks.onDeleted;1637            if (onDeleted) {1638              onDeleted(1639                deletedFiber.stateNode as SuspenseInstance | ActivityInstance,1640              );1641            }1642          } catch (error) {1643            captureCommitPhaseError(1644              deletedFiber,1645              nearestMountedAncestor,1646              error,1647            );1648          }1649        }1650      }16511652      // Dehydrated fragments don't have any children16531654      // Delete the dehydrated suspense boundary and all of its content.1655      // $FlowFixMe[constant-condition]1656      if (supportsMutation) {1657        if (hostParent !== null) {1658          if (hostParentIsContainer) {1659            clearSuspenseBoundaryFromContainer(1660              hostParent as any as Container,1661              deletedFiber.stateNode as SuspenseInstance,1662            );1663          } else {1664            clearSuspenseBoundary(1665              hostParent as any as Instance,1666              deletedFiber.stateNode as SuspenseInstance,1667            );1668          }1669        }1670      }1671      break;1672    }1673    case HostPortal: {1674      // $FlowFixMe[constant-condition]1675      if (supportsMutation) {1676        // When we go into a portal, it becomes the parent to remove from.1677        const prevHostParent = hostParent;1678        const prevHostParentIsContainer = hostParentIsContainer;1679        hostParent = deletedFiber.stateNode.containerInfo;1680        hostParentIsContainer = true;1681        recursivelyTraverseDeletionEffects(1682          finishedRoot,1683          nearestMountedAncestor,1684          deletedFiber,1685        );1686        hostParent = prevHostParent;1687        hostParentIsContainer = prevHostParentIsContainer;1688      } else {1689        // $FlowFixMe[constant-condition]1690        if (supportsPersistence) {1691          commitHostPortalContainerChildren(1692            deletedFiber.stateNode,1693            deletedFiber,1694            createContainerChildSet(),1695          );1696        }16971698        recursivelyTraverseDeletionEffects(1699          finishedRoot,1700          nearestMountedAncestor,1701          deletedFiber,1702        );1703      }1704      break;1705    }1706    case FunctionComponent:1707    case ForwardRef:1708    case MemoComponent:1709    case SimpleMemoComponent: {1710      // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.1711      commitHookEffectListUnmount(1712        HookInsertion,1713        deletedFiber,1714        nearestMountedAncestor,1715      );1716      if (!offscreenSubtreeWasHidden) {1717        commitHookLayoutUnmountEffects(1718          deletedFiber,1719          nearestMountedAncestor,1720          HookLayout,1721        );1722      }1723      recursivelyTraverseDeletionEffects(1724        finishedRoot,1725        nearestMountedAncestor,1726        deletedFiber,1727      );1728      break;1729    }1730    case ClassComponent: {1731      if (!offscreenSubtreeWasHidden) {1732        safelyDetachRef(deletedFiber, nearestMountedAncestor);1733        const instance = deletedFiber.stateNode;1734        if (typeof instance.componentWillUnmount === 'function') {1735          safelyCallComponentWillUnmount(1736            deletedFiber,1737            nearestMountedAncestor,1738            instance,1739          );1740        }1741      }1742      recursivelyTraverseDeletionEffects(1743        finishedRoot,1744        nearestMountedAncestor,1745        deletedFiber,1746      );1747      break;1748    }1749    case ScopeComponent: {1750      if (enableScopeAPI) {1751        if (!offscreenSubtreeWasHidden) {1752          safelyDetachRef(deletedFiber, nearestMountedAncestor);1753        }1754      }1755      recursivelyTraverseDeletionEffects(1756        finishedRoot,1757        nearestMountedAncestor,1758        deletedFiber,1759      );1760      break;1761    }1762    case OffscreenComponent: {1763      if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {1764        // If this offscreen component is hidden, we already unmounted it. Before1765        // deleting the children, track that it's already unmounted so that we1766        // don't attempt to unmount the effects again.1767        // TODO: If the tree is hidden, in most cases we should be able to skip1768        // over the nested children entirely. An exception is we haven't yet found1769        // the topmost host node to delete, which we already track on the stack.1770        // But the other case is portals, which need to be detached no matter how1771        // deeply they are nested. We should use a subtree flag to track whether a1772        // subtree includes a nested portal.1773        const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1774        offscreenSubtreeWasHidden =1775          prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17761777        recursivelyTraverseDeletionEffects(1778          finishedRoot,1779          nearestMountedAncestor,1780          deletedFiber,1781        );1782        offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;1783      } else {1784        recursivelyTraverseDeletionEffects(1785          finishedRoot,1786          nearestMountedAncestor,1787          deletedFiber,1788        );1789      }1790      break;1791    }1792    case ViewTransitionComponent: {1793      if (enableViewTransition) {1794        if (__DEV__) {1795          if (deletedFiber.flags & ViewTransitionNamedStatic) {1796            untrackNamedViewTransition(deletedFiber);1797          }1798        }1799        safelyDetachRef(deletedFiber, nearestMountedAncestor);1800        recursivelyTraverseDeletionEffects(1801          finishedRoot,1802          nearestMountedAncestor,1803          deletedFiber,1804        );1805        break;1806      }1807      // Fallthrough1808    }1809    case Fragment: {1810      if (enableFragmentRefs) {1811        if (!offscreenSubtreeWasHidden) {1812          safelyDetachRef(deletedFiber, nearestMountedAncestor);1813        }1814        recursivelyTraverseDeletionEffects(1815          finishedRoot,1816          nearestMountedAncestor,1817          deletedFiber,1818        );1819        break;1820      }1821      // Fallthrough1822    }1823    default: {1824      recursivelyTraverseDeletionEffects(1825        finishedRoot,1826        nearestMountedAncestor,1827        deletedFiber,1828      );1829      break;1830    }1831  }18321833  if (1834    enableProfilerTimer &&1835    enableProfilerCommitHooks &&1836    enableComponentPerformanceTrack &&1837    (deletedFiber.mode & ProfileMode) !== NoMode &&1838    componentEffectStartTime >= 0 &&1839    componentEffectEndTime >= 0 &&1840    (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)1841  ) {1842    logComponentEffect(1843      deletedFiber,1844      componentEffectStartTime,1845      componentEffectEndTime,1846      componentEffectDuration,1847      componentEffectErrors,1848    );1849  }18501851  popComponentEffectStart(prevEffectStart);1852  popComponentEffectDuration(prevEffectDuration);1853  popComponentEffectErrors(prevEffectErrors);1854  popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1855}18561857function commitSuspenseCallback(finishedWork: Fiber) {1858  // TODO: Delete this feature. It's not properly covered by DEV features.1859  const newState: SuspenseState | null = finishedWork.memoizedState;1860  if (enableSuspenseCallback && newState !== null) {1861    const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;1862    if (typeof suspenseCallback === 'function') {1863      const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;1864      if (retryQueue !== null) {1865        suspenseCallback(new Set(retryQueue));1866      }1867    } else if (__DEV__) {1868      if (suspenseCallback !== undefined) {1869        console.error('Unexpected type for suspenseCallback.');1870      }1871    }1872  }1873}18741875function commitActivityHydrationCallbacks(1876  finishedRoot: FiberRoot,1877  finishedWork: Fiber,1878) {1879  // $FlowFixMe[constant-condition]1880  if (!supportsHydration) {1881    return;1882  }1883  const newState: ActivityState | null = finishedWork.memoizedState;1884  if (newState === null) {1885    const current = finishedWork.alternate;1886    if (current !== null) {1887      const prevState: ActivityState | null = current.memoizedState;1888      if (prevState !== null) {1889        const activityInstance = prevState.dehydrated;1890        commitHostHydratedActivity(activityInstance, finishedWork);1891        if (enableSuspenseCallback) {1892          try {1893            // TODO: Delete this feature. It's not properly covered by DEV features.1894            const hydrationCallbacks = finishedRoot.hydrationCallbacks;1895            if (hydrationCallbacks !== null) {1896              const onHydrated = hydrationCallbacks.onHydrated;1897              if (onHydrated) {1898                onHydrated(activityInstance);1899              }1900            }1901          } catch (error) {1902            captureCommitPhaseError(finishedWork, finishedWork.return, error);1903          }1904        }1905      }1906    }1907  }1908}19091910function commitSuspenseHydrationCallbacks(1911  finishedRoot: FiberRoot,1912  finishedWork: Fiber,1913) {1914  // $FlowFixMe[constant-condition]1915  if (!supportsHydration) {1916    return;1917  }1918  const newState: SuspenseState | null = finishedWork.memoizedState;1919  if (newState === null) {1920    const current = finishedWork.alternate;1921    if (current !== null) {1922      const prevState: SuspenseState | null = current.memoizedState;1923      if (prevState !== null) {1924        const suspenseInstance = prevState.dehydrated;1925        if (suspenseInstance !== null) {1926          commitHostHydratedSuspense(suspenseInstance, finishedWork);1927          if (enableSuspenseCallback) {1928            try {1929              // TODO: Delete this feature. It's not properly covered by DEV features.1930              const hydrationCallbacks = finishedRoot.hydrationCallbacks;1931              if (hydrationCallbacks !== null) {1932                const onHydrated = hydrationCallbacks.onHydrated;1933                if (onHydrated) {1934                  onHydrated(suspenseInstance);1935                }1936              }1937            } catch (error) {1938              captureCommitPhaseError(finishedWork, finishedWork.return, error);1939            }1940          }1941        }1942      }1943    }1944  }1945}19461947function getRetryCache(finishedWork: Fiber) {1948  // TODO: Unify the interface for the retry cache so we don't have to switch1949  // on the tag like this.1950  switch (finishedWork.tag) {1951    case ActivityComponent:1952    case SuspenseComponent:1953    case SuspenseListComponent: {1954      let retryCache = finishedWork.stateNode;1955      if (retryCache === null) {1956        retryCache = finishedWork.stateNode = new PossiblyWeakSet();1957      }1958      return retryCache;1959    }1960    case OffscreenComponent: {1961      const instance: OffscreenInstance = finishedWork.stateNode;1962      let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =1963        instance._retryCache;1964      if (retryCache === null) {1965        retryCache = instance._retryCache = new PossiblyWeakSet();1966      }1967      return retryCache;1968    }1969    default: {1970      throw new Error(1971        `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +1972          'bug in React.',1973      );1974    }1975  }1976}19771978function attachSuspenseRetryListeners(1979  finishedWork: Fiber,1980  wakeables: RetryQueue,1981) {1982  // If this boundary just timed out, then it will have a set of wakeables.1983  // For each wakeable, attach a listener so that when it resolves, React1984  // attempts to re-render the boundary in the primary (pre-timeout) state.1985  const retryCache = getRetryCache(finishedWork);1986  wakeables.forEach(wakeable => {1987    // Memoize using the boundary fiber to prevent redundant listeners.1988    if (!retryCache.has(wakeable)) {1989      retryCache.add(wakeable);19901991      if (enableUpdaterTracking) {1992        if (isDevToolsPresent) {1993          if (inProgressLanes !== null && inProgressRoot !== null) {1994            // If we have pending work still, associate the original updaters with it.1995            restorePendingUpdaters(inProgressRoot, inProgressLanes);1996          } else {1997            throw Error(1998              'Expected finished root and lanes to be set. This is a bug in React.',1999            );2000          }

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.