/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/import type {
Instance,
TextInstance,
SuspenseInstance,
Container,
ChildSet,
UpdatePayload,
HoistableRoot,
} from './ReactFiberConfig';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane';
import {SyncLane} from './ReactFiberLane';
import type {SuspenseState, RetryQueue} from './ReactFiberSuspenseComponent';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
import type {Wakeable} from 'shared/ReactTypes';
import {isOffscreenManual} from './ReactFiberActivityComponent';
import type {
OffscreenState,
OffscreenInstance,
OffscreenQueue,
OffscreenProps,
} from './ReactFiberActivityComponent';
import type {HookFlags} from './ReactHookEffectTags';
import type {Cache} from './ReactFiberCacheComponent';
import type {RootState} from './ReactFiberRoot';
import type {
Transition,
TracingMarkerInstance,
TransitionAbort,
} from './ReactFiberTracingMarkerComponent';
import {
enableCreateEventHandleAPI,
enableProfilerTimer,
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableSchedulingProfiler,
enableSuspenseCallback,
enableScopeAPI,
enableUpdaterTracking,
enableCache,
enableTransitionTracing,
enableUseEffectEventHook,
enableFloat,
enableLegacyHidden,
enableHostSingletons,
alwaysThrottleRetries,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
ForwardRef,
ClassComponent,
HostRoot,
HostComponent,
HostHoistable,
HostSingleton,
HostText,
HostPortal,
Profiler,
SuspenseComponent,
DehydratedFragment,
IncompleteClassComponent,
MemoComponent,
SimpleMemoComponent,
SuspenseListComponent,
ScopeComponent,
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {
NoFlags,
ContentReset,
Placement,
ChildDeletion,
Snapshot,
Update,
Callback,
Ref,
Hydrating,
Passive,
BeforeMutationMask,
MutationMask,
LayoutMask,
PassiveMask,
Visibility,
ShouldSuspendCommit,
MaySuspendCommit,
} from './ReactFiberFlags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
getCurrentFiber as getCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {resolveDefaultProps} from './ReactFiberLazyComponent';
import {
isCurrentUpdateNested,
getCommitTime,
recordLayoutEffectDuration,
startLayoutEffectTimer,
recordPassiveEffectDuration,
startPassiveEffectTimer,
} from './ReactProfilerTimer';
import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
import {
deferHiddenCallbacks,
commitHiddenCallbacks,
commitCallbacks,
} from './ReactFiberClassUpdateQueue';
import {
getPublicInstance,
supportsMutation,
supportsPersistence,
supportsHydration,
supportsResources,
supportsSingletons,
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
appendChildToContainer,
insertBefore,
insertInContainerBefore,
removeChild,
removeChildFromContainer,
clearSuspenseBoundary,
clearSuspenseBoundaryFromContainer,
replaceContainerChildren,
createContainerChildSet,
hideInstance,
hideTextInstance,
unhideInstance,
unhideTextInstance,
commitHydratedContainer,
commitHydratedSuspenseInstance,
clearContainer,
prepareScopeUpdate,
prepareForCommit,
beforeActiveInstanceBlur,
detachDeletedInstance,
clearSingleton,
acquireSingletonInstance,
releaseSingletonInstance,
getHoistableRoot,
acquireResource,
releaseResource,
hydrateHoistable,
mountHoistable,
unmountHoistable,
prepareToCommitHoistables,
suspendInstance,
suspendResource,
} from './ReactFiberConfig';
import {
captureCommitPhaseError,
resolveRetryWakeable,
markCommitTimeOfFallback,
enqueuePendingPassiveProfilerEffect,
restorePendingUpdaters,
addTransitionStartCallbackToPendingTransition,
addTransitionProgressCallbackToPendingTransition,
addTransitionCompleteCallbackToPendingTransition,
addMarkerProgressCallbackToPendingTransition,
addMarkerIncompleteCallbackToPendingTransition,
addMarkerCompleteCallbackToPendingTransition,
setIsRunningInsertionEffect,
getExecutionContext,
CommitContext,
NoContext,
} from './ReactFiberWorkLoop';
import {
NoFlags as NoHookEffect,
HasEffect as HookHasEffect,
Layout as HookLayout,
Insertion as HookInsertion,
Passive as HookPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
import {doesFiberContain} from './ReactFiberTreeReflection';
import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
import {
isDevToolsPresent,
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
markComponentPassiveEffectUnmountStopped,
markComponentLayoutEffectMountStarted,
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
onCommitUnmount,
} from './ReactFiberDevToolsHook';
import {releaseCache, retainCache} from './ReactFiberCacheComponent';
import {clearTransitionsForLanes} from './ReactFiberLane';
import {
OffscreenVisible,
OffscreenDetached,
OffscreenPassiveEffectsConnected,
} from './ReactFiberActivityComponent';
import {
TransitionRoot,
TransitionTracingMarker,
} from './ReactFiberTracingMarkerComponent';
import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
}// Used during the commit phase to track the state of the Offscreen component stack.// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.let offscreenSubtreeIsHidden: boolean = false;
let offscreenSubtreeWasHidden: boolean = false;
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
let nextEffect: Fiber | null = null;
// Used for Profiling builds to track updaters.let inProgressLanes: Lanes | null = null;
let inProgressRoot: FiberRoot | null = null;
function shouldProfile(current: Fiber): boolean {
return (
enableProfilerTimer &&
enableProfilerCommitHooks &&
(current.mode & ProfileMode) !== NoMode &&
(getExecutionContext() & CommitContext) !== NoContext
);}export function reportUncaughtErrorInDEV(error: mixed) {
// Wrapping each small part of the commit phase into a guarded
// callback is a bit too slow (https://github.com/facebook/react/pull/21666).
// But we rely on it to surface errors to DEV tools like overlays
// (https://github.com/facebook/react/issues/21712).
// As a compromise, rethrow only caught errors in a guard.
if (__DEV__) {
invokeGuardedCallback(null, () => {
throw error;
});clearCaughtError();
}}function callComponentWillUnmountWithTimer(current: Fiber, instance: any) {
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
if (shouldProfile(current)) {
try {
startLayoutEffectTimer();
instance.componentWillUnmount();
} finally {
recordLayoutEffectDuration(current);
}} else {
instance.componentWillUnmount();
}}// Capture errors so they don't interrupt unmounting.function safelyCallComponentWillUnmount(
current: Fiber,nearestMountedAncestor: Fiber | null,instance: any,) {try {
callComponentWillUnmountWithTimer(current, instance);
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}}// Capture errors so they don't interrupt mounting.function safelyAttachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
try {
commitAttachRef(current);
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}}function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
const ref = current.ref;
const refCleanup = current.refCleanup;
if (ref !== null) {
if (typeof refCleanup === 'function') {
try {
if (shouldProfile(current)) {
try {
startLayoutEffectTimer();
refCleanup();
} finally {
recordLayoutEffectDuration(current);
}} else {
refCleanup();
}} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
} finally {
// `refCleanup` has been called. Nullify all references to it to prevent double invocation.
current.refCleanup = null;
const finishedWork = current.alternate;
if (finishedWork != null) {
finishedWork.refCleanup = null;
}}} else if (typeof ref === 'function') {
let retVal;
try {
if (shouldProfile(current)) {
try {
startLayoutEffectTimer();
retVal = ref(null);
} finally {
recordLayoutEffectDuration(current);
}} else {
retVal = ref(null);
}} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}if (__DEV__) {
if (typeof retVal === 'function') {
console.error(
'Unexpected return value from a callback ref in %s. ' +
'A callback ref should not return a function.',
getComponentNameFromFiber(current),
);}}} else {
// $FlowFixMe[incompatible-use] unable to narrow type to RefObject
ref.current = null;
}}}function safelyCallDestroy(
current: Fiber,nearestMountedAncestor: Fiber | null,destroy: () => void,
) {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}}let focusedInstanceHandle: null | Fiber = null;
let shouldFireAfterActiveInstanceBlur: boolean = false;
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
): boolean {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
// We no longer need to track the active instance fiber
const shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
// This phase is only used for beforeActiveInstanceBlur.
// Let's skip the whole loop if it's off.
if (enableCreateEventHandleAPI) {
// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
commitBeforeMutationEffectsDeletion(deletion);
}}}const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}}}function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}resetCurrentDebugFiberInDEV();
const sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}nextEffect = fiber.return;
}}function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if (enableCreateEventHandleAPI) {
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
finishedWork.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
// $FlowFixMe[incompatible-call] found when upgrading Flow
doesFiberContain(finishedWork, focusedInstanceHandle)
) {shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(finishedWork);
}}}if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(finishedWork);
}switch (finishedWork.tag) {
case FunctionComponent: {
if (enableUseEffectEventHook) {
if ((flags & Update) !== NoFlags) {
commitUseEffectEventMount(finishedWork);
}}break;
}case ForwardRef:
case SimpleMemoComponent: {
break;
}case ClassComponent: {
if ((flags & Snapshot) !== NoFlags) {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}}}const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);if (__DEV__) {
const didWarnSet =
((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
console.error(
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
'must be returned. You have returned undefined.',
getComponentNameFromFiber(finishedWork),
);}}instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}}break;
}case HostRoot: {
if ((flags & Snapshot) !== NoFlags) {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}}break;
}case HostComponent:
case HostHoistable:
case HostSingleton:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default: {
if ((flags & Snapshot) !== NoFlags) {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +'likely caused by a bug in React. Please file an issue.',);}}}if ((flags & Snapshot) !== NoFlags) {
resetCurrentDebugFiberInDEV();
}}function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
if (enableCreateEventHandleAPI) {
// TODO (effects) It would be nice to avoid calling doesFiberContain()
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
// Use it to store which part of the tree the focused instance is in?
// This assumes we can safely determine that instance during the "render" phase.
if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(deletion);
}}}function commitHookEffectListUnmount(
flags: HookFlags,finishedWork: Fiber,nearestMountedAncestor: Fiber | null,) {const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const inst = effect.inst;
const destroy = inst.destroy;
if (destroy !== undefined) {
inst.destroy = undefined;
if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
markComponentPassiveEffectUnmountStarted(finishedWork);
} else if ((flags & HookLayout) !== NoHookEffect) {
markComponentLayoutEffectUnmountStarted(finishedWork);
}}if (__DEV__) {
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(true);
}}safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
if (__DEV__) {
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(false);
}}if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
markComponentPassiveEffectUnmountStopped();
} else if ((flags & HookLayout) !== NoHookEffect) {
markComponentLayoutEffectUnmountStopped();
}}}}effect = effect.next;
} while (effect !== firstEffect);
}}function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
markComponentPassiveEffectMountStarted(finishedWork);
} else if ((flags & HookLayout) !== NoHookEffect) {
markComponentLayoutEffectMountStarted(finishedWork);
}}// Mount
const create = effect.create;
if (__DEV__) {
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(true);
}}const inst = effect.inst;
const destroy = create();
inst.destroy = destroy;
if (__DEV__) {
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(false);
}}if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
markComponentPassiveEffectMountStopped();
} else if ((flags & HookLayout) !== NoHookEffect) {
markComponentLayoutEffectMountStopped();
}}if (__DEV__) {
if (destroy !== undefined && typeof destroy !== 'function') {
let hookName;
if ((effect.tag & HookLayout) !== NoFlags) {
hookName = 'useLayoutEffect';
} else if ((effect.tag & HookInsertion) !== NoFlags) {
hookName = 'useInsertionEffect';
} else {
hookName = 'useEffect';
}let addendum;
if (destroy === null) {
addendum =
' You returned null. If your effect does not require clean ' +
'up, return undefined (or nothing).';
} else if (typeof destroy.then === 'function') {
addendum =
'\n\nIt looks like you wrote ' +
hookName +
'(async () => ...) or returned a Promise. ' +
'Instead, write the async function inside your effect ' +
'and call it immediately:\n\n' +
hookName +
'(() => {\n' +
' async function fetchData() {\n' +
' // You can await here\n' +
' const response = await MyAPI.getData(someId);\n' +
' // ...\n' +
' }\n' +
' fetchData();\n' +
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching';
} else {
addendum = ' You returned: ' + destroy;
}console.error(
'%s must not return anything besides a function, ' +
'which is used for clean-up.%s',
hookName,
addendum,
);}}}effect = effect.next;
} while (effect !== firstEffect);
}}function commitUseEffectEventMount(finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
if (eventPayloads !== null) {
for (let ii = 0; ii < eventPayloads.length; ii++) {
const {ref, nextImpl} = eventPayloads[ii];
ref.impl = nextImpl;
}}}export function commitPassiveEffectDurations(
finishedRoot: FiberRoot,finishedWork: Fiber,): void {if (
enableProfilerTimer &&enableProfilerCommitHooks &&getExecutionContext() & CommitContext
) {// Only Profilers with work in their subtree will have an Update effect scheduled.
if ((finishedWork.flags & Update) !== NoFlags) {
switch (finishedWork.tag) {
case Profiler: {
const {passiveEffectDuration} = finishedWork.stateNode;
const {id, onPostCommit} = finishedWork.memoizedProps;
// This value will still reflect the previous commit phase.
// It does not get reset until the start of the next commit phase.
const commitTime = getCommitTime();
let phase = finishedWork.alternate === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}}if (typeof onPostCommit === 'function') {
onPostCommit(id, phase, passiveEffectDuration, commitTime);
}// Bubble times to the next nearest ancestor Profiler.
// After we process that Profiler, we'll bubble further up.
let parentFiber = finishedWork.return;
outer: while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
const root = parentFiber.stateNode;
root.passiveEffectDuration += passiveEffectDuration;
break outer;
case Profiler:
const parentStateNode = parentFiber.stateNode;
parentStateNode.passiveEffectDuration += passiveEffectDuration;
break outer;
}parentFiber = parentFiber.return;
}break;
}default:break;
}}}}function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}recordLayoutEffectDuration(finishedWork);
} else {
try {
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}function commitClassLayoutLifecycles(
finishedWork: Fiber,current: Fiber | null,) {const instance = finishedWork.stateNode;
if (current === null) {
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}}}if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}recordLayoutEffectDuration(finishedWork);
} else {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}}}if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}recordLayoutEffectDuration(finishedWork);
} else {
try {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}}function commitClassCallbacks(finishedWork: Fiber) {
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
const instance = finishedWork.stateNode;
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);}}}// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
try {
commitCallbacks(updateQueue, instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}function commitHostComponentMount(finishedWork: Fiber) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
const instance: Instance = finishedWork.stateNode;
try {
commitMount(instance, type, props, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
if (enableProfilerTimer && getExecutionContext() & CommitContext) {
try {
const {onCommit, onRender} = finishedWork.memoizedProps;
const {effectDuration} = finishedWork.stateNode;
const commitTime = getCommitTime();
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}}if (typeof onRender === 'function') {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);}if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);}// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
enqueuePendingPassiveProfilerEffect(finishedWork);
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to read them first.
let parentFiber = finishedWork.return;
outer: while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
const root = parentFiber.stateNode;
root.effectDuration += effectDuration;
break outer;
case Profiler:
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break outer;
}parentFiber = parentFiber.return;
}}} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,current: Fiber | null,finishedWork: Fiber,committedLanes: Lanes,): void {// When updating this function, also update reappearLayoutEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible.
const flags = finishedWork.flags;switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}break;
}case ClassComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);if (flags & Update) {
commitClassLayoutLifecycles(finishedWork, current);
}if (flags & Callback) {
commitClassCallbacks(finishedWork);
}if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}break;
}case HostRoot: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);if (flags & Callback) {
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostSingleton:
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}}try {
commitCallbacks(updateQueue, instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}break;
}case HostHoistable: {
if (enableFloat && supportsResources) {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}break;
}// Fall through
}case HostSingleton:
case HostComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && flags & Update) {
commitHostComponentMount(finishedWork);
}if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}break;
}case Profiler: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);// TODO: Should this fire inside an offscreen tree? Or should it wait to
// fire when the tree becomes visible again.
if (flags & Update) {
commitProfilerUpdate(finishedWork, current);
}break;
}case SuspenseComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);if (flags & Update) {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
}break;
}case OffscreenComponent: {
const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
if (isModernRoot) {
const isHidden = finishedWork.memoizedState !== null;
const newOffscreenSubtreeIsHidden =
isHidden || offscreenSubtreeIsHidden;
if (newOffscreenSubtreeIsHidden) {
// The Offscreen tree is hidden. Skip over its layout effects.
} else {
// The Offscreen tree is visible.
const wasHidden = current !== null && current.memoizedState !== null;
const newOffscreenSubtreeWasHidden =
wasHidden || offscreenSubtreeWasHidden;
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
// This is the root of a reappearing boundary. As we continue
// traversing the layout effects, we must also re-mount layout
// effects that were unmounted when the Offscreen subtree was
// hidden. So this is a superset of the normal commitLayoutEffects.
const includeWorkInProgressEffects =
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);}offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
}} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);}if (flags & Ref) {
const props: OffscreenProps = finishedWork.memoizedProps;
if (props.mode === 'manual') {
safelyAttachRef(finishedWork, finishedWork.return);
} else {
safelyDetachRef(finishedWork, finishedWork.return);
}}break;
}default: {recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);break;
}}}function abortRootTransitions(
root: FiberRoot,abort: TransitionAbort,deletedTransitions: Set<Transition>,deletedOffscreenInstance: OffscreenInstance | null,isInDeletedTree: boolean,) {if (enableTransitionTracing) {
const rootTransitions = root.incompleteTransitions;
deletedTransitions.forEach(transition => {
if (rootTransitions.has(transition)) {
const transitionInstance: TracingMarkerInstance = (rootTransitions.get(
transition,
): any);
if (transitionInstance.aborts === null) {
transitionInstance.aborts = [];
}transitionInstance.aborts.push(abort);
if (deletedOffscreenInstance !== null) {
if (
transitionInstance.pendingBoundaries !== null &&
transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)
) {// $FlowFixMe[incompatible-use] found when upgrading Flow
transitionInstance.pendingBoundaries.delete(
deletedOffscreenInstance,
);}}}});}}function abortTracingMarkerTransitions(
abortedFiber: Fiber,abort: TransitionAbort,deletedTransitions: Set<Transition>,deletedOffscreenInstance: OffscreenInstance | null,isInDeletedTree: boolean,) {if (enableTransitionTracing) {
const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;
const markerTransitions = markerInstance.transitions;
const pendingBoundaries = markerInstance.pendingBoundaries;
if (markerTransitions !== null) {
// TODO: Refactor this code. Is there a way to move this code to
// the deletions phase instead of calculating it here while making sure
// complete is called appropriately?
deletedTransitions.forEach(transition => {
// If one of the transitions on the tracing marker is a transition
// that was in an aborted subtree, we will abort that tracing marker
if (
abortedFiber !== null &&
markerTransitions.has(transition) &&
(markerInstance.aborts === null ||
!markerInstance.aborts.includes(abort))
) {if (markerInstance.transitions !== null) {
if (markerInstance.aborts === null) {
markerInstance.aborts = [abort];
addMarkerIncompleteCallbackToPendingTransition(
abortedFiber.memoizedProps.name,
markerInstance.transitions,
markerInstance.aborts,
);} else {
markerInstance.aborts.push(abort);
}// We only want to call onTransitionProgress when the marker hasn't been
// deleted
if (
deletedOffscreenInstance !== null &&
!isInDeletedTree &&
pendingBoundaries !== null &&
pendingBoundaries.has(deletedOffscreenInstance)
) {pendingBoundaries.delete(deletedOffscreenInstance);
addMarkerProgressCallbackToPendingTransition(
abortedFiber.memoizedProps.name,
deletedTransitions,
pendingBoundaries,
);}}}});}}}function abortParentMarkerTransitionsForDeletedFiber(
abortedFiber: Fiber,abort: TransitionAbort,deletedTransitions: Set<Transition>,deletedOffscreenInstance: OffscreenInstance | null,isInDeletedTree: boolean,) {if (enableTransitionTracing) {
// Find all pending markers that are waiting on child suspense boundaries in the
// aborted subtree and cancels them
let fiber: null | Fiber = abortedFiber;
while (fiber !== null) {
switch (fiber.tag) {
case TracingMarkerComponent:
abortTracingMarkerTransitions(
fiber,
abort,
deletedTransitions,
deletedOffscreenInstance,
isInDeletedTree,
);break;
case HostRoot:
const root = fiber.stateNode;
abortRootTransitions(
root,abort,
deletedTransitions,
deletedOffscreenInstance,
isInDeletedTree,
);break;
default:
break;
}fiber = fiber.return;
}}}function commitTransitionProgress(offscreenFiber: Fiber) {
if (enableTransitionTracing) {
// This function adds suspense boundaries to the root
// or tracing marker's pendingBoundaries map.
// When a suspense boundary goes from a resolved to a fallback
// state we add the boundary to the map, and when it goes from
// a fallback to a resolved state, we remove the boundary from
// the map.
// We use stateNode on the Offscreen component as a stable object
// that doesnt change from render to render. This way we can
// distinguish between different Offscreen instances (vs. the same
// Offscreen instance with different fibers)
const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
let prevState: SuspenseState | null = null;
const previousFiber = offscreenFiber.alternate;
if (previousFiber !== null && previousFiber.memoizedState !== null) {
prevState = previousFiber.memoizedState;
}const nextState: SuspenseState | null = offscreenFiber.memoizedState;
const wasHidden = prevState !== null;
const isHidden = nextState !== null;
const pendingMarkers = offscreenInstance._pendingMarkers;
// If there is a name on the suspense boundary, store that in
// the pending boundaries.
let name = null;
const parent = offscreenFiber.return;
if (
parent !== null &&
parent.tag === SuspenseComponent &&
parent.memoizedProps.unstable_name
) {name = parent.memoizedProps.unstable_name;
}if (!wasHidden && isHidden) {
// The suspense boundaries was just hidden. Add the boundary
// to the pending boundary set if it's there
if (pendingMarkers !== null) {
pendingMarkers.forEach(markerInstance => {
const pendingBoundaries = markerInstance.pendingBoundaries;
const transitions = markerInstance.transitions;
const markerName = markerInstance.name;
if (
pendingBoundaries !== null &&
!pendingBoundaries.has(offscreenInstance)
) {pendingBoundaries.set(offscreenInstance, {
name,
});if (transitions !== null) {
if (
markerInstance.tag === TransitionTracingMarker &&
markerName !== null
) {addMarkerProgressCallbackToPendingTransition(
markerName,
transitions,
pendingBoundaries,
);} else if (markerInstance.tag === TransitionRoot) {
transitions.forEach(transition => {
addTransitionProgressCallbackToPendingTransition(
transition,
pendingBoundaries,
);});}}}});}} else if (wasHidden && !isHidden) {
// The suspense boundary went from hidden to visible. Remove
// the boundary from the pending suspense boundaries set
// if it's there
if (pendingMarkers !== null) {
pendingMarkers.forEach(markerInstance => {
const pendingBoundaries = markerInstance.pendingBoundaries;
const transitions = markerInstance.transitions;
const markerName = markerInstance.name;
if (
pendingBoundaries !== null &&
pendingBoundaries.has(offscreenInstance)
) {pendingBoundaries.delete(offscreenInstance);
if (transitions !== null) {
if (
markerInstance.tag === TransitionTracingMarker &&
markerName !== null
) {addMarkerProgressCallbackToPendingTransition(
markerName,
transitions,
pendingBoundaries,
);// If there are no more unresolved suspense boundaries, the interaction
// is considered finished
if (pendingBoundaries.size === 0) {
if (markerInstance.aborts === null) {
addMarkerCompleteCallbackToPendingTransition(
markerName,
transitions,
);}markerInstance.transitions = null;
markerInstance.pendingBoundaries = null;
markerInstance.aborts = null;
}} else if (markerInstance.tag === TransitionRoot) {
transitions.forEach(transition => {
addTransitionProgressCallbackToPendingTransition(
transition,
pendingBoundaries,
);});}}}});}}}}function hideOrUnhideAllChildren(finishedWork: Fiber, isHidden: boolean) {
// Only hide or unhide the top-most host nodes.
let hostSubtreeRoot = null;
if (supportsMutation) {
// We only have the top Fiber that was inserted but we need to recurse down its
// children to find all the terminal nodes.
let node: Fiber = finishedWork;
while (true) {
if (
node.tag === HostComponent ||
(enableFloat && supportsResources
? node.tag === HostHoistable
: false) ||
(enableHostSingletons && supportsSingletons
? node.tag === HostSingleton
: false)
) {if (hostSubtreeRoot === null) {
hostSubtreeRoot = node;
try {
const instance = node.stateNode;
if (isHidden) {
hideInstance(instance);
} else {
unhideInstance(node.stateNode, node.memoizedProps);
}} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}} else if (node.tag === HostText) {
if (hostSubtreeRoot === null) {
try {
const instance = node.stateNode;
if (isHidden) {
hideTextInstance(instance);
} else {
unhideTextInstance(instance, node.memoizedProps);
}} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}} else if (
(node.tag === OffscreenComponent ||
node.tag === LegacyHiddenComponent) &&
(node.memoizedState: OffscreenState) !== null &&
node !== finishedWork
) {// Found a nested Offscreen component that is hidden.
// Don't search any deeper. This tree should remain hidden.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}if (node === finishedWork) {
return;
}while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}if (hostSubtreeRoot === node) {
hostSubtreeRoot = null;
}node = node.return;
}if (hostSubtreeRoot === node) {
hostSubtreeRoot = null;
}node.sibling.return = node.return;
node = node.sibling;
}}}function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
let instanceToUse;
switch (finishedWork.tag) {
case HostHoistable:
case HostSingleton:
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}// Moved outside to ensure DCE works with this flag
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
instanceToUse = instance;
}if (typeof ref === 'function') {
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
finishedWork.refCleanup = ref(instanceToUse);
} finally {
recordLayoutEffectDuration(finishedWork);
}} else {
finishedWork.refCleanup = ref(instanceToUse);
}} else {
if (__DEV__) {
if (!ref.hasOwnProperty('current')) {
console.error(
'Unexpected ref object provided for %s. ' +
'Use either a ref-setter function or React.createRef().',
getComponentNameFromFiber(finishedWork),
);}}// $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
ref.current = instanceToUse;
}}}function detachFiberMutation(fiber: Fiber) {
// Cut off the return pointer to disconnect it from the tree.
// This enables us to detect and warn against state updates on an unmounted component.
// It also prevents events from bubbling from within disconnected components.
//
// Ideally, we should also clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child.
// This child itself will be GC:ed when the parent updates the next time.
//
// Note that we can't clear child or sibling pointers yet.
// They're needed for passive effects and for findDOMNode.
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
//
// Don't reset the alternate yet, either. We need that so we can detach the
// alternate's fields in the passive phase. Clearing the return pointer is
// sufficient for findDOMNode semantics.
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.return = null;
}fiber.return = null;
}function detachFiberAfterEffects(fiber: Fiber) {
const alternate = fiber.alternate;
if (alternate !== null) {
fiber.alternate = null;
detachFiberAfterEffects(alternate);
}// Clear cyclical Fiber fields. This level alone is designed to roughly
// approximate the planned Fiber refactor. In that world, `setState` will be
// bound to a special "instance" object instead of a Fiber. The Instance
// object will not have any of these fields. It will only be connected to
// the fiber tree via a single link at the root. So if this level alone is
// sufficient to fix memory issues, that bodes well for our plans.
fiber.child = null;
fiber.deletions = null;
fiber.sibling = null;
// The `stateNode` is cyclical because on host nodes it points to the host
// tree, which has its own pointers to children, parents, and siblings.
// The other host nodes also point back to fibers, so we should detach that
// one, too.
if (fiber.tag === HostComponent) {
const hostInstance: Instance = fiber.stateNode;
if (hostInstance !== null) {
detachDeletedInstance(hostInstance);
}}fiber.stateNode = null;
if (__DEV__) {
fiber._debugSource = null;
fiber._debugOwner = null;
}// Theoretically, nothing in here should be necessary, because we already
// disconnected the fiber from the tree. So even if something leaks this
// particular fiber, it won't leak anything else.
fiber.return = null;
fiber.dependencies = null;
fiber.memoizedProps = null;
fiber.memoizedState = null;
fiber.pendingProps = null;
fiber.stateNode = null;
// TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
fiber.updateQueue = null;
}function emptyPortalContainer(current: Fiber) {
if (!supportsPersistence) {
return;
}const portal: {
containerInfo: Container,
pendingChildren: ChildSet,
...} = current.stateNode;
const {containerInfo} = portal;
const emptyChildSet = createContainerChildSet();
replaceContainerChildren(containerInfo, emptyChildSet);
}function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}parent = parent.return;
}throw new Error(
'Expected to find a host parent. This error is likely caused by a bug ' +'in React. Please file an issue.',);}function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
(enableFloat && supportsResources ? fiber.tag === HostHoistable : false) ||
(enableHostSingletons && supportsSingletons
? fiber.tag === HostSingleton
: false) ||
fiber.tag === HostPortal
);}function getHostSibling(fiber: Fiber): ?Instance {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
let node: Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}// $FlowFixMe[incompatible-type] found when upgrading Flow
node = node.return;
}node.sibling.return = node.return;node = node.sibling;while (
node.tag !== HostComponent &&
node.tag !== HostText &&
(!(enableHostSingletons && supportsSingletons)
? true
: node.tag !== HostSingleton) &&
node.tag !== DehydratedFragment
) {// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
if (node.flags & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
node.child.return = node;
node = node.child;
}}// Check if this host node is stable or about to be placed.
if (!(node.flags & Placement)) {
// Found it!
return node.stateNode;}}}function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}if (enableHostSingletons && supportsSingletons) {
if (finishedWork.tag === HostSingleton) {
// Singletons are already in the Host and don't need to be placed
// Since they operate somewhat like Portals though their children will
// have Placement and will get placed inside them
return;
}}// Recursively insert all host nodes into the parent.
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostSingleton: {
if (enableHostSingletons && supportsSingletons) {
const parent: Instance = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need to recurse down its
// children to find all the terminal nodes.
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}// Fall through
}case HostComponent: {
const parent: Instance = parentFiber.stateNode;
if (parentFiber.flags & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.flags &= ~ContentReset;
}const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need to recurse down its
// children to find all the terminal nodes.
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}case HostRoot:
case HostPortal: {
const parent: Container = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
break;
}default:throw new Error(
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);}}function insertOrAppendPlacementNodeIntoContainer(
node: Fiber,before: ?Instance,parent: Container,): void {const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {appendChildToContainer(parent, stateNode);
}} else if (
tag === HostPortal ||
(enableHostSingletons && supportsSingletons ? tag === HostSingleton : false)
) {// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
// If the insertion is a HostSingleton then it will be placed independently
} else {
const child = node.child;if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}}}}function insertOrAppendPlacementNode(
node: Fiber,before: ?Instance,parent: Instance,): void {const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;if (before) {
insertBefore(parent, stateNode, before);
} else {appendChild(parent, stateNode);
}} else if (
tag === HostPortal ||
(enableHostSingletons && supportsSingletons ? tag === HostSingleton : false)
) {// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
// If the insertion is a HostSingleton then it will be placed independently
} else {
const child = node.child;if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}}}}// These are tracked on the stack as we recursively traverse a// deleted subtree.// TODO: Update these during the whole mutation phase, not just during// a deletion.let hostParent: Instance | Container | null = null;
let hostParentIsContainer: boolean = false;
function commitDeletionEffects(
root: FiberRoot,returnFiber: Fiber,deletedFiber: Fiber,) {if (supportsMutation) {
// We only have the top Fiber that was deleted but we need to recurse down its
// children to find all the terminal nodes.
// Recursively delete all host nodes from the parent, detach refs, clean
// up mounted layout effects, and call componentWillUnmount.
// We only need to remove the topmost host child in each branch. But then we
// still need to keep traversing to unmount effects, refs, and cWU. TODO: We
// could split this into two separate traversals functions, where the second
// one doesn't include any removeChild logic. This is maybe the same
// function as "disappearLayoutEffects" (or whatever that turns into after
// the layout phase is refactored to use recursion).
// Before starting, find the nearest host parent on the stack so we know
// which instance/container to remove the children from.
// TODO: Instead of searching up the fiber return path on every deletion, we
// can track the nearest host component on the JS stack as we traverse the
// tree during the commit phase. This would make insertions faster, too.
let parent: null | Fiber = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostSingleton:
case HostComponent: {
hostParent = parent.stateNode;
hostParentIsContainer = false;
break findParent;
}case HostRoot: {
hostParent = parent.stateNode.containerInfo;
hostParentIsContainer = true;
break findParent;
}case HostPortal: {
hostParent = parent.stateNode.containerInfo;
hostParentIsContainer = true;
break findParent;
}}parent = parent.return;
}if (hostParent === null) {
throw new Error(
'Expected to find a host parent. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);}commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
hostParentIsContainer = false;
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
}detachFiberMutation(deletedFiber);
}function recursivelyTraverseDeletionEffects(
finishedRoot: FiberRoot,nearestMountedAncestor: Fiber,parent: Fiber,) {// TODO: Use a static flag to skip trees that don't have unmount effects
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}}function commitDeletionEffectsOnFiber(
finishedRoot: FiberRoot,nearestMountedAncestor: Fiber,deletedFiber: Fiber,) {onCommitUnmount(deletedFiber);
// The cases in this outer switch modify the stack before they traverse
// into their subtree. There are simpler cases in the inner switch
// that don't modify the stack.
switch (deletedFiber.tag) {
case HostHoistable: {
if (enableFloat && supportsResources) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);if (deletedFiber.memoizedState) {
releaseResource(deletedFiber.memoizedState);
} else if (deletedFiber.stateNode) {
unmountHoistable(deletedFiber.stateNode);
}return;
}// Fall through
}case HostSingleton: {
if (enableHostSingletons && supportsSingletons) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = deletedFiber.stateNode;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);// Normally this is called in passive unmount effect phase however with
// HostSingleton we warn if you acquire one that is already associated to
// a different fiber. To increase our chances of avoiding this, specifically
// if you keyed a HostSingleton so there will be a delete followed by a Placement
// we treat detach eagerly here
releaseSingletonInstance(deletedFiber.stateNode);
hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
return;
}// Fall through
}case HostComponent: {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}// Intentional fallthrough to next branch
}case HostText: {
// We only need to remove the nearest host child. Set the host parent
// to `null` on the stack to indicate that nested children don't
// need to be removed.
if (supportsMutation) {
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = null;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
if (hostParent !== null) {
// Now that all the child effects have unmounted, we can remove the
// node from the tree.
if (hostParentIsContainer) {
removeChildFromContainer(
((hostParent: any): Container),
(deletedFiber.stateNode: Instance | TextInstance),
);} else {
removeChild(
((hostParent: any): Instance),
(deletedFiber.stateNode: Instance | TextInstance),
);}}} else {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);}return;
}case DehydratedFragment: {
if (enableSuspenseCallback) {
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
const onDeleted = hydrationCallbacks.onDeleted;
if (onDeleted) {
onDeleted((deletedFiber.stateNode: SuspenseInstance));
}}}// Dehydrated fragments don't have any children
// Delete the dehydrated suspense boundary and all of its content.
if (supportsMutation) {
if (hostParent !== null) {
if (hostParentIsContainer) {
clearSuspenseBoundaryFromContainer(
((hostParent: any): Container),
(deletedFiber.stateNode: SuspenseInstance),
);} else {
clearSuspenseBoundary(
((hostParent: any): Instance),
(deletedFiber.stateNode: SuspenseInstance),
);}}}return;
}case HostPortal: {
if (supportsMutation) {
// When we go into a portal, it becomes the parent to remove from.
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = deletedFiber.stateNode.containerInfo;
hostParentIsContainer = true;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
} else {
emptyPortalContainer(deletedFiber);
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);}return;
}case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
if (!offscreenSubtreeWasHidden) {
const updateQueue: FunctionComponentUpdateQueue | null =
(deletedFiber.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const tag = effect.tag;
const inst = effect.inst;
const destroy = inst.destroy;
if (destroy !== undefined) {
if ((tag & HookInsertion) !== NoHookEffect) {
inst.destroy = undefined;
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);} else if ((tag & HookLayout) !== NoHookEffect) {
if (enableSchedulingProfiler) {
markComponentLayoutEffectUnmountStarted(deletedFiber);
}if (shouldProfile(deletedFiber)) {
startLayoutEffectTimer();
inst.destroy = undefined;
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);recordLayoutEffectDuration(deletedFiber);
} else {
inst.destroy = undefined;
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);}if (enableSchedulingProfiler) {
markComponentLayoutEffectUnmountStopped();
}}}effect = effect.next;
} while (effect !== firstEffect);
}}}recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);return;
}case ClassComponent: {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
const instance = deletedFiber.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
deletedFiber,
nearestMountedAncestor,
instance,
);}}recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);return;
}case ScopeComponent: {
if (enableScopeAPI) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);return;
}case OffscreenComponent: {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
if (deletedFiber.mode & ConcurrentMode) {
// If this offscreen component is hidden, we already unmounted it. Before
// deleting the children, track that it's already unmounted so that we
// don't attempt to unmount the effects again.
// TODO: If the tree is hidden, in most cases we should be able to skip
// over the nested children entirely. An exception is we haven't yet found
// the topmost host node to delete, which we already track on the stack.
// But the other case is portals, which need to be detached no matter how
// deeply they are nested. We should use a subtree flag to track whether a
// subtree includes a nested portal.
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeWasHidden =
prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
} else {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);}break;
}default: {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);return;
}}}function commitSuspenseCallback(finishedWork: Fiber) {
// TODO: Move this to passive phase
const newState: SuspenseState | null = finishedWork.memoizedState;
if (enableSuspenseCallback && newState !== null) {
const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
if (typeof suspenseCallback === 'function') {
const retryQueue: RetryQueue | null = (finishedWork.updateQueue: any);
if (retryQueue !== null) {
suspenseCallback(new Set(retryQueue));
}} else if (__DEV__) {
if (suspenseCallback !== undefined) {
console.error('Unexpected type for suspenseCallback.');
}}}}function commitSuspenseHydrationCallbacks(
finishedRoot: FiberRoot,finishedWork: Fiber,) {if (!supportsHydration) {
return;
}const newState: SuspenseState | null = finishedWork.memoizedState;
if (newState === null) {
const current = finishedWork.alternate;
if (current !== null) {
const prevState: SuspenseState | null = current.memoizedState;
if (prevState !== null) {
const suspenseInstance = prevState.dehydrated;
if (suspenseInstance !== null) {
try {
commitHydratedSuspenseInstance(suspenseInstance);
if (enableSuspenseCallback) {
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
const onHydrated = hydrationCallbacks.onHydrated;
if (onHydrated) {
onHydrated(suspenseInstance);
}}}} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}}}}function getRetryCache(finishedWork: Fiber) {
// TODO: Unify the interface for the retry cache so we don't have to switch
// on the tag like this.
switch (finishedWork.tag) {
case SuspenseComponent:
case SuspenseListComponent: {
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}return retryCache;
}case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =
instance._retryCache;
if (retryCache === null) {
retryCache = instance._retryCache = new PossiblyWeakSet();
}return retryCache;
}default: {
throw new Error(
`Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +
'bug in React.',
);}}}export function detachOffscreenInstance(instance: OffscreenInstance): void {
const fiber = instance._current;if (fiber === null) {
throw new Error(
'Calling Offscreen.detach before instance handle has been set.',
);}if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
// The instance is already detached, this is a noop.
return;}// TODO: There is an opportunity to optimise this by not entering commit phase
// and unmounting effects directly.
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._pendingVisibility |= OffscreenDetached;scheduleUpdateOnFiber(root, fiber, SyncLane);
}}export function attachOffscreenInstance(instance: OffscreenInstance): void {
const fiber = instance._current;if (fiber === null) {
throw new Error(
'Calling Offscreen.detach before instance handle has been set.',
);}if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
// The instance is already attached, this is a noop.
return;}const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._pendingVisibility &= ~OffscreenDetached;scheduleUpdateOnFiber(root, fiber, SyncLane);
}}function attachSuspenseRetryListeners(
finishedWork: Fiber,wakeables: RetryQueue,) {// If this boundary just timed out, then it will have a set of wakeables.
// For each wakeable, attach a listener so that when it resolves, React
// attempts to re-render the boundary in the primary (pre-timeout) state.
const retryCache = getRetryCache(finishedWork);
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
if (!retryCache.has(wakeable)) {
retryCache.add(wakeable);
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);}}}wakeable.then(retry, retry);
}});}// This function detects when a Suspense boundary goes from visible to hidden.// It returns false if the boundary is already hidden.// TODO: Use an effect tag.export function isSuspenseBoundaryBeingHidden(
current: Fiber | null,finishedWork: Fiber,): boolean {
if (current !== null) {
const oldState: SuspenseState | null = current.memoizedState;
if (oldState === null || oldState.dehydrated !== null) {
const newState: SuspenseState | null = finishedWork.memoizedState;
return newState !== null && newState.dehydrated === null;
}}return false;}export function commitMutationEffects(
root: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,) {inProgressLanes = committedLanes;
inProgressRoot = root;
setCurrentDebugFiberInDEV(finishedWork);
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
setCurrentDebugFiberInDEV(finishedWork);
inProgressLanes = null;
inProgressRoot = null;
}function recursivelyTraverseMutationEffects(
root: FiberRoot,parentFiber: Fiber,lanes: Lanes,) {// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects hae fired.
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
try {
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}}}const prevDebugFiber = getCurrentDebugFiberInDEV();
if (parentFiber.subtreeFlags & MutationMask) {
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
commitMutationEffectsOnFiber(child, root, lanes);
child = child.sibling;
}}setCurrentDebugFiberInDEV(prevDebugFiber);
}let currentHoistableRoot: HoistableRoot | null = null;
function commitMutationEffectsOnFiber(
finishedWork: Fiber,root: FiberRoot,lanes: Lanes,) {const current = finishedWork.alternate;
const flags = finishedWork.flags;
// The effect flag should be checked *after* we refine the type of fiber,
// because the fiber tag is more specific. An exception is any flag related
// to reconciliation, because those can be set on all fiber types.
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
try {
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);commitHookEffectListMount(
HookInsertion | HookHasEffect,
finishedWork,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}// Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}recordLayoutEffectDuration(finishedWork);
} else {
try {
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}return;
}case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}}if (flags & Callback && offscreenSubtreeIsHidden) {
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
deferHiddenCallbacks(updateQueue);
}}return;
}case HostHoistable: {
if (enableFloat && supportsResources) {
// We cast because we always set the root at the React root and so it cannot be
// null while we are processing mutation effects
const hoistableRoot: HoistableRoot = (currentHoistableRoot: any);
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}}if (flags & Update) {
const currentResource =
current !== null ? current.memoizedState : null;
const newResource = finishedWork.memoizedState;
if (current === null) {
// We are mounting a new HostHoistable Fiber. We fork the mount
// behavior based on whether this instance is a Hoistable Instance
// or a Hoistable Resource
if (newResource === null) {
if (finishedWork.stateNode === null) {
finishedWork.stateNode = hydrateHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.memoizedProps,
finishedWork,
);} else {
mountHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.stateNode,
);}} else {
finishedWork.stateNode = acquireResource(
hoistableRoot,
newResource,
finishedWork.memoizedProps,
);}} else if (currentResource !== newResource) {
// We are moving to or from Hoistable Resource, or between different Hoistable Resources
if (currentResource === null) {
if (current.stateNode !== null) {
unmountHoistable(current.stateNode);
}} else {
releaseResource(currentResource);
}if (newResource === null) {
mountHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.stateNode,
);} else {
acquireResource(
hoistableRoot,
newResource,
finishedWork.memoizedProps,
);}} else if (newResource === null && finishedWork.stateNode !== null) {
// We may have an update on a Hoistable element
const updatePayload: null | UpdatePayload =
(finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
try {
commitUpdate(
finishedWork.stateNode,
updatePayload,
finishedWork.type,
current.memoizedProps,
finishedWork.memoizedProps,
finishedWork,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}return;
}// Fall through
}case HostSingleton: {
if (enableHostSingletons && supportsSingletons) {
if (flags & Update) {
const previousWork = finishedWork.alternate;
if (previousWork === null) {
const singleton = finishedWork.stateNode;
const props = finishedWork.memoizedProps;
// This was a new mount, we need to clear and set initial properties
clearSingleton(singleton);
acquireSingletonInstance(
finishedWork.type,
props,
singleton,
finishedWork,
);}}}// Fall through
}case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}}if (supportsMutation) {
// TODO: ContentReset gets cleared by the children during the commit
// phase. This is a refactor hazard because it means we must read
// flags the flags after `commitReconciliationEffects` has already run;
// the order matters. We should refactor so that ContentReset does not
// rely on mutating the flag during commit. Like by setting a flag
// during the render phase instead.
if (finishedWork.flags & ContentReset) {
const instance: Instance = finishedWork.stateNode;
try {
resetTextContent(instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}if (flags & Update) {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps =
current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | UpdatePayload =
(finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
try {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}}return;
}case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
if (supportsMutation) {
if (finishedWork.stateNode === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);}const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
try {
commitTextUpdate(textInstance, oldText, newText);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}return;
}case HostRoot: {
if (enableFloat && supportsResources) {
prepareToCommitHoistables();
const previousHoistableRoot = currentHoistableRoot;
currentHoistableRoot = getHoistableRoot(root.containerInfo);
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
currentHoistableRoot = previousHoistableRoot;
commitReconciliationEffects(finishedWork);
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
}if (flags & Update) {
if (supportsMutation && supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
try {
commitHydratedContainer(root.containerInfo);
} catch (error) {
captureCommitPhaseError(
finishedWork,
finishedWork.return,
error,
);}}}}if (supportsPersistence) {
const containerInfo = root.containerInfo;
const pendingChildren = root.pendingChildren;
try {
replaceContainerChildren(containerInfo, pendingChildren);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}return;
}case HostPortal: {
if (enableFloat && supportsResources) {
const previousHoistableRoot = currentHoistableRoot;
currentHoistableRoot = getHoistableRoot(
finishedWork.stateNode.containerInfo,
);recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
currentHoistableRoot = previousHoistableRoot;
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
}if (flags & Update) {
if (supportsPersistence) {
const portal = finishedWork.stateNode;
const containerInfo = portal.containerInfo;
const pendingChildren = portal.pendingChildren;
try {
replaceContainerChildren(containerInfo, pendingChildren);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}return;
}case SuspenseComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// TODO: We should mark a flag on the Suspense fiber itself, rather than
// relying on the Offscreen fiber having a flag also being marked. The
// reason is that this offscreen fiber might not be part of the work-in-
// progress tree! It could have been reused from a previous render. This
// doesn't lead to incorrect behavior because we don't rely on the flag
// check alone; we also compare the states explicitly below. But for
// modeling purposes, we _should_ be able to rely on the flag check alone.
// So this is a bit fragile.
//
// Also, all this logic could/should move to the passive phase so it
// doesn't block paint.
const offscreenFiber: Fiber = (finishedWork.child: any);
if (offscreenFiber.flags & Visibility) {
// Throttle the appearance and disappearance of Suspense fallbacks.
const isShowingFallback =
(finishedWork.memoizedState: SuspenseState | null) !== null;
const wasShowingFallback =
current !== null &&
(current.memoizedState: SuspenseState | null) !== null;
if (alwaysThrottleRetries) {
if (isShowingFallback !== wasShowingFallback) {
// A fallback is either appearing or disappearing.
markCommitTimeOfFallback();
}} else {
if (isShowingFallback && !wasShowingFallback) {
// Old behavior. Only mark when a fallback appears, not when
// it disappears.
markCommitTimeOfFallback();
}}}if (flags & Update) {
try {
commitSuspenseCallback(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}const retryQueue: RetryQueue | null = (finishedWork.updateQueue: any);
if (retryQueue !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}}return;
}case OffscreenComponent: {
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}}const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const wasHidden = current !== null && current.memoizedState !== null;
if (finishedWork.mode & ConcurrentMode) {
// Before committing the children, track on the stack whether this
// offscreen subtree was already hidden, so that we don't unmount the
// effects again.
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
}commitReconciliationEffects(finishedWork);
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
// TODO: Add explicit effect flag to set _current.
offscreenInstance._current = finishedWork;
// Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
// to support batching of `attach` and `detach` calls.
offscreenInstance._visibility &= ~OffscreenDetached;
offscreenInstance._visibility |=
offscreenInstance._pendingVisibility & OffscreenDetached;
if (flags & Visibility) {
// Track the current state on the Offscreen instance so we can
// read it during an event
if (isHidden) {
offscreenInstance._visibility &= ~OffscreenVisible;
} else {
offscreenInstance._visibility |= OffscreenVisible;
}if (isHidden) {
const isUpdate = current !== null;
const wasHiddenByAncestorOffscreen =
offscreenSubtreeIsHidden || offscreenSubtreeWasHidden;
// Only trigger disapper layout effects if:
// - This is an update, not first mount.
// - This Offscreen was not hidden before.
// - Ancestor Offscreen was not hidden in previous commit.
if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) {
if ((finishedWork.mode & ConcurrentMode) !== NoMode) {
// Disappear the layout effects of all the children
recursivelyTraverseDisappearLayoutEffects(finishedWork);
}}} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}}// Offscreen with manual mode manages visibility manually.
if (supportsMutation && !isOffscreenManual(finishedWork)) {
// TODO: This needs to run whenever there's an insertion or update
// inside a hidden Offscreen tree.
hideOrUnhideAllChildren(finishedWork, isHidden);
}}// TODO: Move to passive phase
if (flags & Update) {
const offscreenQueue: OffscreenQueue | null =
(finishedWork.updateQueue: any);
if (offscreenQueue !== null) {
const retryQueue = offscreenQueue.retryQueue;
if (retryQueue !== null) {
offscreenQueue.retryQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}}}return;
}case SuspenseListComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
const retryQueue: Set<Wakeable> | null =
(finishedWork.updateQueue: any);
if (retryQueue !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}}return;
}case ScopeComponent: {
if (enableScopeAPI) {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(finishedWork, finishedWork.return);
}safelyAttachRef(finishedWork, finishedWork.return);
}if (flags & Update) {
const scopeInstance = finishedWork.stateNode;
prepareScopeUpdate(scopeInstance, finishedWork);
}}return;
}default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
return;
}}}function commitReconciliationEffects(finishedWork: Fiber) {
// Placement effects (insertions, reorders) can be scheduled on any fiber
// type. They needs to happen after the children effects have fired, but
// before the effects on this fiber have fired.
const flags = finishedWork.flags;
if (flags & Placement) {
try {
commitPlacement(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement;
}if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}}export function commitLayoutEffects(
finishedWork: Fiber,root: FiberRoot,committedLanes: Lanes,): void {inProgressLanes = committedLanes;inProgressRoot = root;const current = finishedWork.alternate;commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
inProgressLanes = null;inProgressRoot = null;}function recursivelyTraverseLayoutEffects(
root: FiberRoot,parentFiber: Fiber,lanes: Lanes,) {const prevDebugFiber = getCurrentDebugFiberInDEV();
if (parentFiber.subtreeFlags & LayoutMask) {
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
const current = child.alternate;
commitLayoutEffectOnFiber(root, current, child, lanes);
child = child.sibling;
}}setCurrentDebugFiberInDEV(prevDebugFiber);
}export function disappearLayoutEffects(finishedWork: Fiber) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// TODO (Offscreen) Check: flags & LayoutStatic
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
commitHookEffectListUnmount(
HookLayout,
finishedWork,
finishedWork.return,
);} finally {
recordLayoutEffectDuration(finishedWork);
}} else {
commitHookEffectListUnmount(
HookLayout,
finishedWork,
finishedWork.return,
);}recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}case ClassComponent: {
// TODO (Offscreen) Check: flags & RefStatic
safelyDetachRef(finishedWork, finishedWork.return);
const instance = finishedWork.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
finishedWork,
finishedWork.return,
instance,
);}recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}case HostHoistable:
case HostSingleton:
case HostComponent: {
// TODO (Offscreen) Check: flags & RefStatic
safelyDetachRef(finishedWork, finishedWork.return);
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}case OffscreenComponent: {
// TODO (Offscreen) Check: flags & RefStatic
safelyDetachRef(finishedWork, finishedWork.return);
const isHidden = finishedWork.memoizedState !== null;
if (isHidden) {
// Nested Offscreen tree is already hidden. Don't disappear
// its effects.
} else {
recursivelyTraverseDisappearLayoutEffects(finishedWork);
}break;
}default: {
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}}}function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
let child = parentFiber.child;
while (child !== null) {
disappearLayoutEffects(child);
child = child.sibling;
}}export function reappearLayoutEffects(
finishedRoot: FiberRoot,current: Fiber | null,finishedWork: Fiber,// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
includeWorkInProgressEffects: boolean,) {// Turn on layout effects in a tree that previously disappeared.
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);// TODO: Check flags & LayoutStatic
commitHookLayoutEffects(finishedWork, HookLayout);
break;
}case ClassComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);// TODO: Check for LayoutStatic flag
const instance = finishedWork.stateNode;
if (typeof instance.componentDidMount === 'function') {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}// Commit any callbacks that would have fired while the component
// was hidden.
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
commitHiddenCallbacks(updateQueue, instance);
}// If this is newly finished work, check for setState callbacks
if (includeWorkInProgressEffects && flags & Callback) {
commitClassCallbacks(finishedWork);
}// TODO: Check flags & RefStatic
safelyAttachRef(finishedWork, finishedWork.return);
break;
}// Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
// because this function only visits nodes that are inside an
// Offscreen fiber.
// case HostRoot: {
// ...
// }
case HostHoistable:
case HostSingleton:
case HostComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (includeWorkInProgressEffects && current === null && flags & Update) {
commitHostComponentMount(finishedWork);
}// TODO: Check flags & Ref
safelyAttachRef(finishedWork, finishedWork.return);
break;
}case Profiler: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);// TODO: Figure out how Profiler updates should work with Offscreen
if (includeWorkInProgressEffects && flags & Update) {
commitProfilerUpdate(finishedWork, current);
}break;
}case SuspenseComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);// TODO: Figure out how Suspense hydration callbacks should work
// with Offscreen.
if (includeWorkInProgressEffects && flags & Update) {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
}break;
}case OffscreenComponent: {
const offscreenState: OffscreenState = finishedWork.memoizedState;
const isHidden = offscreenState !== null;
if (isHidden) {
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
} else {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);}// TODO: Check flags & Ref
safelyAttachRef(finishedWork, finishedWork.return);
break;
}default: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);break;
}}}function recursivelyTraverseReappearLayoutEffects(
finishedRoot: FiberRoot,parentFiber: Fiber,includeWorkInProgressEffects: boolean,) {// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
const childShouldIncludeWorkInProgressEffects =
includeWorkInProgressEffects &&
(parentFiber.subtreeFlags & LayoutMask) !== NoFlags;
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
const prevDebugFiber = getCurrentDebugFiberInDEV();
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
reappearLayoutEffects(
finishedRoot,
current,
child,
childShouldIncludeWorkInProgressEffects,
);child = child.sibling;
}setCurrentDebugFiberInDEV(prevDebugFiber);
}function commitHookPassiveMountEffects(
finishedWork: Fiber,hookFlags: HookFlags,) {if (shouldProfile(finishedWork)) {
startPassiveEffectTimer();
try {
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}recordPassiveEffectDuration(finishedWork);
} else {
try {
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}}}function commitOffscreenPassiveMountEffects(
current: Fiber | null,finishedWork: Fiber,instance: OffscreenInstance,) {if (enableCache) {
let previousCache: Cache | null = null;
if (
current !== null &&
current.memoizedState !== null &&
current.memoizedState.cachePool !== null
) {previousCache = current.memoizedState.cachePool.pool;
}let nextCache: Cache | null = null;
if (
finishedWork.memoizedState !== null &&
finishedWork.memoizedState.cachePool !== null
) {nextCache = finishedWork.memoizedState.cachePool.pool;
}// Retain/release the cache used for pending (suspended) nodes.
// Note that this is only reached in the non-suspended/visible case:
// when the content is suspended/hidden, the retain/release occurs
// via the parent Suspense component (see case above).
if (nextCache !== previousCache) {
if (nextCache != null) {
retainCache(nextCache);
}if (previousCache != null) {
releaseCache(previousCache);
}}}if (enableTransitionTracing) {
// TODO: Pre-rendering should not be counted as part of a transition. We
// may add separate logs for pre-rendering, but it's not part of the
// primary metrics.
const offscreenState: OffscreenState = finishedWork.memoizedState;
const queue: OffscreenQueue | null = (finishedWork.updateQueue: any);
const isHidden = offscreenState !== null;
if (queue !== null) {
if (isHidden) {
const transitions = queue.transitions;
if (transitions !== null) {
transitions.forEach(transition => {
// Add all the transitions saved in the update queue during
// the render phase (ie the transitions associated with this boundary)
// into the transitions set.
if (instance._transitions === null) {
instance._transitions = new Set();
}instance._transitions.add(transition);
});}const markerInstances = queue.markerInstances;
if (markerInstances !== null) {
markerInstances.forEach(markerInstance => {
const markerTransitions = markerInstance.transitions;
// There should only be a few tracing marker transitions because
// they should be only associated with the transition that
// caused them
if (markerTransitions !== null) {
markerTransitions.forEach(transition => {
if (instance._transitions === null) {
instance._transitions = new Set();
} else if (instance._transitions.has(transition)) {
if (markerInstance.pendingBoundaries === null) {
markerInstance.pendingBoundaries = new Map();
}if (instance._pendingMarkers === null) {
instance._pendingMarkers = new Set();
}instance._pendingMarkers.add(markerInstance);
}});}});}}finishedWork.updateQueue = null;
}commitTransitionProgress(finishedWork);
// TODO: Refactor this into an if/else branch
if (!isHidden) {
instance._transitions = null;
instance._pendingMarkers = null;
}}}function commitCachePassiveMountEffect(
current: Fiber | null,finishedWork: Fiber,) {if (enableCache) {
let previousCache: Cache | null = null;
if (finishedWork.alternate !== null) {
previousCache = finishedWork.alternate.memoizedState.cache;
}const nextCache = finishedWork.memoizedState.cache;
// Retain/release the cache. In theory the cache component
// could be "borrowing" a cache instance owned by some parent,
// in which case we could avoid retaining/releasing. But it
// is non-trivial to determine when that is the case, so we
// always retain/release.
if (nextCache !== previousCache) {
retainCache(nextCache);
if (previousCache != null) {
releaseCache(previousCache);
}}}}function commitTracingMarkerPassiveMountEffect(finishedWork: Fiber) {
// Get the transitions that were initiatized during the render
// and add a start transition callback for each of them
// We will only call this on initial mount of the tracing marker
// only if there are no suspense children
const instance = finishedWork.stateNode;
if (instance.transitions !== null && instance.pendingBoundaries === null) {
addMarkerCompleteCallbackToPendingTransition(
finishedWork.memoizedProps.name,
instance.transitions,
);instance.transitions = null;
instance.pendingBoundaries = null;
instance.aborts = null;
instance.name = null;
}}export function commitPassiveMountEffects(
root: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,): void {setCurrentDebugFiberInDEV(finishedWork);
commitPassiveMountOnFiber(
root,finishedWork,committedLanes,committedTransitions,);resetCurrentDebugFiberInDEV();
}function recursivelyTraversePassiveMountEffects(
root: FiberRoot,parentFiber: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,) {const prevDebugFiber = getCurrentDebugFiberInDEV();
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
commitPassiveMountOnFiber(
root,child,
committedLanes,
committedTransitions,
);child = child.sibling;
}}setCurrentDebugFiberInDEV(prevDebugFiber);
}function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,): void {// When updating this function, also update reconnectPassiveEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible,
// or when toggling effects inside a hidden tree.
const flags = finishedWork.flags;switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
commitHookPassiveMountEffects(
finishedWork,
HookPassive | HookHasEffect,
);}break;
}case HostRoot: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
if (enableCache) {
let previousCache: Cache | null = null;
if (finishedWork.alternate !== null) {
previousCache = finishedWork.alternate.memoizedState.cache;
}const nextCache = finishedWork.memoizedState.cache;
// Retain/release the root cache.
// Note that on initial mount, previousCache and nextCache will be the same
// and this retain won't occur. To counter this, we instead retain the HostRoot's
// initial cache when creating the root itself (see createFiberRoot() in
// ReactFiberRoot.js). Subsequent updates that change the cache are reflected
// here, such that previous/next caches are retained correctly.
if (nextCache !== previousCache) {
retainCache(nextCache);
if (previousCache != null) {
releaseCache(previousCache);
}}}if (enableTransitionTracing) {
// Get the transitions that were initiatized during the render
// and add a start transition callback for each of them
const root: FiberRoot = finishedWork.stateNode;
const incompleteTransitions = root.incompleteTransitions;
// Initial render
if (committedTransitions !== null) {
committedTransitions.forEach(transition => {
addTransitionStartCallbackToPendingTransition(transition);
});clearTransitionsForLanes(finishedRoot, committedLanes);
}incompleteTransitions.forEach((markerInstance, transition) => {
const pendingBoundaries = markerInstance.pendingBoundaries;
if (pendingBoundaries === null || pendingBoundaries.size === 0) {
if (markerInstance.aborts === null) {
addTransitionCompleteCallbackToPendingTransition(transition);
}incompleteTransitions.delete(transition);
}});clearTransitionsForLanes(finishedRoot, committedLanes);
}}break;
}case LegacyHiddenComponent: {
if (enableLegacyHidden) {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
const current = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}}break;
}case OffscreenComponent: {
// TODO: Pass `current` as argument to this function
const instance: OffscreenInstance = finishedWork.stateNode;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (isHidden) {
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);} else {
if (finishedWork.mode & ConcurrentMode) {
// The effects are currently disconnected. Since the tree is hidden,
// don't connect them. This also applies to the initial render.
if (enableCache || enableTransitionTracing) {
// "Atomic" effects are ones that need to fire on every commit,
// even during pre-rendering. An example is updating the reference
// count on cache instances.
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);}} else {
// Legacy Mode: Fire the effects even if the tree is hidden.
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);}}} else {
// Tree is visible
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);} else {
// The effects are currently disconnected. Reconnect them, while also
// firing effects inside newly mounted trees. This also applies to
// the initial render.
instance._visibility |= OffscreenPassiveEffectsConnected;
const includeWorkInProgressEffects =
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags;
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);}}if (flags & Passive) {
const current = finishedWork.alternate;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}break;
}case CacheComponent: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
// TODO: Pass `current` as argument to this function
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}break;
}case TracingMarkerComponent: {
if (enableTransitionTracing) {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
commitTracingMarkerPassiveMountEffect(finishedWork);
}break;
}// Intentional fallthrough to next branch
}default: {recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);break;
}}}function recursivelyTraverseReconnectPassiveEffects(
finishedRoot: FiberRoot,parentFiber: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,includeWorkInProgressEffects: boolean,) {// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
const childShouldIncludeWorkInProgressEffects =
includeWorkInProgressEffects &&
(parentFiber.subtreeFlags & PassiveMask) !== NoFlags;
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
const prevDebugFiber = getCurrentDebugFiberInDEV();
let child = parentFiber.child;
while (child !== null) {
reconnectPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
childShouldIncludeWorkInProgressEffects,
);child = child.sibling;
}setCurrentDebugFiberInDEV(prevDebugFiber);
}export function reconnectPassiveEffects(
finishedRoot: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
includeWorkInProgressEffects: boolean,) {const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);// TODO: Check for PassiveStatic flag
commitHookPassiveMountEffects(finishedWork, HookPassive);
break;
}// Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
// because this function only visits nodes that are inside an
// Offscreen fiber.
// case HostRoot: {
// ...
// }
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
const current: Fiber | null = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}}break;
}case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (isHidden) {
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);} else {
if (finishedWork.mode & ConcurrentMode) {
// The effects are currently disconnected. Since the tree is hidden,
// don't connect them. This also applies to the initial render.
if (enableCache || enableTransitionTracing) {
// "Atomic" effects are ones that need to fire on every commit,
// even during pre-rendering. An example is updating the reference
// count on cache instances.
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);}} else {
// Legacy Mode: Fire the effects even if the tree is hidden.
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);}}} else {
// Tree is visible
// Since we're already inside a reconnecting tree, it doesn't matter
// whether the effects are currently connected. In either case, we'll
// continue traversing the tree and firing all the effects.
//
// We do need to set the "connected" flag on the instance, though.
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);}if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
const current: Fiber | null = finishedWork.alternate;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}break;
}case CacheComponent: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}break;
}case TracingMarkerComponent: {
if (enableTransitionTracing) {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);if (includeWorkInProgressEffects && flags & Passive) {
commitTracingMarkerPassiveMountEffect(finishedWork);
}break;
}// Intentional fallthrough to next branch
}default: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
);break;
}}}function recursivelyTraverseAtomicPassiveEffects(
finishedRoot: FiberRoot,parentFiber: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,) {// "Atomic" effects are ones that need to fire on every commit, even during
// pre-rendering. We call this function when traversing a hidden tree whose
// regular effects are currently disconnected.
const prevDebugFiber = getCurrentDebugFiberInDEV();
// TODO: Add special flag for atomic effects
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
commitAtomicPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
);child = child.sibling;
}}setCurrentDebugFiberInDEV(prevDebugFiber);
}function commitAtomicPassiveEffects(
finishedRoot: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,committedTransitions: Array<Transition> | null,) {// "Atomic" effects are ones that need to fire on every commit, even during
// pre-rendering. We call this function when traversing a hidden tree whose
// regular effects are currently disconnected.
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case OffscreenComponent: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
// TODO: Pass `current` as argument to this function
const current = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}break;
}case CacheComponent: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);if (flags & Passive) {
// TODO: Pass `current` as argument to this function
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}break;
}default: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
);break;
}}}export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
setCurrentDebugFiberInDEV(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentDebugFiberInDEV();
}// If we're inside a brand new tree, or a tree that was already visible, then we// should only suspend host components that have a ShouldSuspendCommit flag.// Components without it haven't changed since the last commit, so we can skip// over those.//// When we enter a tree that is being revealed (going from hidden -> visible),// we need to suspend _any_ component that _may_ suspend. Even if they're// already in the "current" tree. Because their visibility has changed, the// browser may not have prerendered them yet. So we check the MaySuspendCommit// flag instead.let suspenseyCommitFlag = ShouldSuspendCommit;
export function accumulateSuspenseyCommit(finishedWork: Fiber): void {
accumulateSuspenseyCommitOnFiber(finishedWork);
}function recursivelyAccumulateSuspenseyCommit(parentFiber: Fiber): void {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
let child = parentFiber.child;
while (child !== null) {
accumulateSuspenseyCommitOnFiber(child);
child = child.sibling;
}}}function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
switch (fiber.tag) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
// This should always be set by visiting HostRoot first
(currentHoistableRoot: any),
fiber.memoizedState,
fiber.memoizedProps,
);} else {
const type = fiber.type;
const props = fiber.memoizedProps;
suspendInstance(type, props);
}}break;
}case HostComponent: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & suspenseyCommitFlag) {
const type = fiber.type;
const props = fiber.memoizedProps;
suspendInstance(type, props);
}break;
}case HostRoot:
case HostPortal: {
if (enableFloat && supportsResources) {
const previousHoistableRoot = currentHoistableRoot;
const container: Container = fiber.stateNode.containerInfo;
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(fiber);
currentHoistableRoot = previousHoistableRoot;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}break;
}case OffscreenComponent: {
const isHidden = (fiber.memoizedState: OffscreenState | null) !== null;
if (isHidden) {
// Don't suspend in hidden trees
} else {
const current = fiber.alternate;
const wasHidden =
current !== null &&
(current.memoizedState: OffscreenState | null) !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
const prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}}break;
}default: {
recursivelyAccumulateSuspenseyCommit(fiber);
}}}function detachAlternateSiblings(parentFiber: Fiber) {
// A fiber was deleted from this parent fiber, but it's still part of the
// previous (alternate) parent fiber's list of children. Because children
// are a linked list, an earlier sibling that's still alive will be
// connected to the deleted fiber via its `alternate`:
//
// live fiber --alternate--> previous live fiber --sibling--> deleted
// fiber
//
// We can't disconnect `alternate` on nodes that haven't been deleted yet,
// but we can disconnect the `sibling` and `child` pointers.
const previousFiber = parentFiber.alternate;
if (previousFiber !== null) {
let detachedChild = previousFiber.child;
if (detachedChild !== null) {
previousFiber.child = null;
do {
// $FlowFixMe[incompatible-use] found when upgrading Flow
const detachedSibling = detachedChild.sibling;
// $FlowFixMe[incompatible-use] found when upgrading Flow
detachedChild.sibling = null;
detachedChild = detachedSibling;
} while (detachedChild !== null);
}}}function commitHookPassiveUnmountEffects(
finishedWork: Fiber,nearestMountedAncestor: null | Fiber,hookFlags: HookFlags,) {if (shouldProfile(finishedWork)) {
startPassiveEffectTimer();
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);recordPassiveEffectDuration(finishedWork);
} else {
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);}}function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects have fired.
const deletions = parentFiber.deletions;if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
// TODO: Convert this to use recursion
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);}}detachAlternateSiblings(parentFiber);
}const prevDebugFiber = getCurrentDebugFiberInDEV();
// TODO: Split PassiveMask into separate masks for mount and unmount?
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;while (child !== null) {
setCurrentDebugFiberInDEV(child);
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}}setCurrentDebugFiberInDEV(prevDebugFiber);
}function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(
finishedWork,
finishedWork.return,
HookPassive | HookHasEffect,
);}break;
}case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (
isHidden &&
instance._visibility & OffscreenPassiveEffectsConnected &&
// For backwards compatibility, don't unmount when a tree suspends. In
// the future we may change this to unmount after a delay.
(finishedWork.return === null ||
finishedWork.return.tag !== SuspenseComponent)
) {// The effects are currently connected. Disconnect them.
// TODO: Add option or heuristic to delay before disconnecting the
// effects. Then if the tree reappears before the delay has elapsed, we
// can skip toggling the effects entirely.
instance._visibility &= ~OffscreenPassiveEffectsConnected;
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
} else {
recursivelyTraversePassiveUnmountEffects(finishedWork);
}break;
}default: {recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}}}function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects have fired.
const deletions = parentFiber.deletions;if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
// TODO: Convert this to use recursion
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);}}detachAlternateSiblings(parentFiber);
}const prevDebugFiber = getCurrentDebugFiberInDEV();
// TODO: Check PassiveStatic flag
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
disconnectPassiveEffect(child);
child = child.sibling;}setCurrentDebugFiberInDEV(prevDebugFiber);
}export function disconnectPassiveEffect(finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// TODO: Check PassiveStatic flag
commitHookPassiveUnmountEffects(
finishedWork,
finishedWork.return,
HookPassive,
);// When disconnecting passive effects, we fire the effects in the same
// order as during a deletiong: parent before child
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
break;
}case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
if (instance._visibility & OffscreenPassiveEffectsConnected) {
instance._visibility &= ~OffscreenPassiveEffectsConnected;
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
} else {
// The effects are already disconnected.
}break;
}default: {recursivelyTraverseDisconnectPassiveEffects(finishedWork);
break;
}}}function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
deletedSubtreeRoot: Fiber,nearestMountedAncestor: Fiber | null,) {while (nextEffect !== null) {
const fiber = nextEffect;
// Deletion effects fire in parent -> child order
// TODO: Check if fiber has a PassiveStatic flag
setCurrentDebugFiberInDEV(fiber);
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
resetCurrentDebugFiberInDEV();
const child = fiber.child;
// TODO: Only traverse subtree if it has a PassiveStatic flag.
if (child !== null) {
child.return = fiber;
nextEffect = child;
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot,
);}}}function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot: Fiber,) {while (nextEffect !== null) {
const fiber = nextEffect;
const sibling = fiber.sibling;
const returnFiber = fiber.return;
// Recursively traverse the entire deleted tree and clean up fiber fields.
// This is more aggressive than ideal, and the long term goal is to only
// have to detach the deleted tree at the root.
detachFiberAfterEffects(fiber);
if (fiber === deletedSubtreeRoot) {
nextEffect = null;
return;
}if (sibling !== null) {
sibling.return = returnFiber;
nextEffect = sibling;
return;
}nextEffect = returnFiber;
}}function commitPassiveUnmountInsideDeletedTreeOnFiber(
current: Fiber,nearestMountedAncestor: Fiber | null,): void {switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookPassiveUnmountEffects(
current,
nearestMountedAncestor,
HookPassive,
);break;
}// TODO: run passive unmount effects when unmounting a root.
// Because passive unmount effects are not currently run,
// the cache instance owned by the root will never be freed.
// When effects are run, the cache should be freed here:
// case HostRoot: {
// if (enableCache) {
// const cache = current.memoizedState.cache;
// releaseCache(cache);
// }
// break;
// }
case LegacyHiddenComponent:
case OffscreenComponent: {
if (enableCache) {
if (
current.memoizedState !== null &&
current.memoizedState.cachePool !== null
) {const cache: Cache = current.memoizedState.cachePool.pool;
// Retain/release the cache used for pending (suspended) nodes.
// Note that this is only reached in the non-suspended/visible case:
// when the content is suspended/hidden, the retain/release occurs
// via the parent Suspense component (see case above).
if (cache != null) {
retainCache(cache);
}}}break;
}case SuspenseComponent: {
if (enableTransitionTracing) {
// We need to mark this fiber's parents as deleted
const offscreenFiber: Fiber = (current.child: any);
const instance: OffscreenInstance = offscreenFiber.stateNode;
const transitions = instance._transitions;
if (transitions !== null) {
const abortReason = {
reason: 'suspense',
name: current.memoizedProps.unstable_name || null,
};if (
current.memoizedState === null ||
current.memoizedState.dehydrated === null
) {abortParentMarkerTransitionsForDeletedFiber(
offscreenFiber,
abortReason,
transitions,
instance,
true,
);if (nearestMountedAncestor !== null) {
abortParentMarkerTransitionsForDeletedFiber(
nearestMountedAncestor,
abortReason,
transitions,
instance,
false,
);}}}}break;
}case CacheComponent: {
if (enableCache) {
const cache = current.memoizedState.cache;
releaseCache(cache);
}break;
}case TracingMarkerComponent: {
if (enableTransitionTracing) {
// We need to mark this fiber's parents as deleted
const instance: TracingMarkerInstance = current.stateNode;
const transitions = instance.transitions;
if (transitions !== null) {
const abortReason = {
reason: 'marker',
name: current.memoizedProps.name,
};abortParentMarkerTransitionsForDeletedFiber(
current,
abortReason,
transitions,
null,
true,
);if (nearestMountedAncestor !== null) {
abortParentMarkerTransitionsForDeletedFiber(
nearestMountedAncestor,
abortReason,
transitions,
null,
false,
);}}}break;
}}}function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
try {
commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}break;
}case ClassComponent: {
const instance = fiber.stateNode;
if (typeof instance.componentDidMount === 'function') {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}}break;
}}}}function invokePassiveEffectMountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
try {
commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}break;
}}}}function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
try {
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
fiber,
fiber.return,
);} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}break;
}case ClassComponent: {
const instance = fiber.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(fiber, fiber.return, instance);
}break;
}}}}function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
try {
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
fiber,
fiber.return,
);} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}}}}}export {
commitPlacement,
commitAttachRef,
invokeLayoutEffectMountInDEV,
invokeLayoutEffectUnmountInDEV,
invokePassiveEffectMountInDEV,
invokePassiveEffectUnmountInDEV,
};