import type {Fiber} from './ReactInternalTypes';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
import type {HookFlags} from './ReactHookEffectTags';
import {
enableProfilerTimer,
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableSchedulingProfiler,
enableScopeAPI,
disableStringRefs,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
HostComponent,
HostHoistable,
HostSingleton,
ScopeComponent,
} from './ReactWorkTags';
import {NoFlags} from './ReactFiberFlags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {resolveClassComponentProps} from './ReactFiberClassComponent';
import {
recordEffectDuration,
startEffectTimer,
isCurrentUpdateNested,
} from './ReactProfilerTimer';
import {NoMode, ProfileMode} from './ReactTypeOfMode';
import {
commitCallbacks,
commitHiddenCallbacks,
} from './ReactFiberClassUpdateQueue';
import {getPublicInstance} from './ReactFiberConfig';
import {
captureCommitPhaseError,
setIsRunningInsertionEffect,
} from './ReactFiberWorkLoop';
import {
NoFlags as NoHookEffect,
Layout as HookLayout,
Insertion as HookInsertion,
Passive as HookPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
import {
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
markComponentPassiveEffectUnmountStopped,
markComponentLayoutEffectMountStarted,
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
} from './ReactFiberDevToolsHook';
import {
callComponentDidMountInDEV,
callComponentDidUpdateInDEV,
callComponentWillUnmountInDEV,
callCreateInDEV,
callDestroyInDEV,
} from './ReactFiberCallUserSpace';
import {runWithFiberInDEV} from './ReactCurrentFiber';
function shouldProfile(current: Fiber): boolean {
return (
enableProfilerTimer &&
enableProfilerCommitHooks &&
(current.mode & ProfileMode) !== NoMode
);
}
export function commitHookLayoutEffects(
finishedWork: Fiber,
hookFlags: HookFlags,
) {
if (shouldProfile(finishedWork)) {
startEffectTimer();
commitHookEffectListMount(hookFlags, finishedWork);
recordEffectDuration(finishedWork);
} else {
commitHookEffectListMount(hookFlags, finishedWork);
}
}
export function commitHookLayoutUnmountEffects(
finishedWork: Fiber,
nearestMountedAncestor: null | Fiber,
hookFlags: HookFlags,
) {
if (shouldProfile(finishedWork)) {
startEffectTimer();
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);
recordEffectDuration(finishedWork);
} else {
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);
}
}
export function commitHookEffectListMount(
flags: HookFlags,
finishedWork: Fiber,
) {
try {
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);
}
}
let destroy;
if (__DEV__) {
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(true);
}
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(false);
}
} else {
const create = effect.create;
const inst = effect.inst;
destroy = create();
inst.destroy = destroy;
}
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://react.dev/link/hooks-data-fetching';
} else {
addendum = ' You returned: ' + destroy;
}
runWithFiberInDEV(
finishedWork,
(n, a) => {
console.error(
'%s must not return anything besides a function, ' +
'which is used for clean-up.%s',
n,
a,
);
},
hookName,
addendum,
);
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
export function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
try {
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) {
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);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
export function commitHookPassiveMountEffects(
finishedWork: Fiber,
hookFlags: HookFlags,
) {
if (shouldProfile(finishedWork)) {
startEffectTimer();
commitHookEffectListMount(hookFlags, finishedWork);
recordEffectDuration(finishedWork);
} else {
commitHookEffectListMount(hookFlags, finishedWork);
}
}
export function commitHookPassiveUnmountEffects(
finishedWork: Fiber,
nearestMountedAncestor: null | Fiber,
hookFlags: HookFlags,
) {
if (shouldProfile(finishedWork)) {
startEffectTimer();
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);
recordEffectDuration(finishedWork);
} else {
commitHookEffectListUnmount(
hookFlags,
finishedWork,
nearestMountedAncestor,
);
}
}
export function commitClassLayoutLifecycles(
finishedWork: Fiber,
current: Fiber | null,
) {
const instance = finishedWork.stateNode;
if (current === null) {
if (__DEV__) {
if (
!finishedWork.type.defaultProps &&
!('ref' in finishedWork.memoizedProps) &&
!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)) {
startEffectTimer();
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
callComponentDidMountInDEV,
finishedWork,
instance,
);
} else {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
recordEffectDuration(finishedWork);
} else {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
callComponentDidMountInDEV,
finishedWork,
instance,
);
} else {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
} else {
const prevProps = resolveClassComponentProps(
finishedWork.type,
current.memoizedProps,
finishedWork.elementType === finishedWork.type,
);
const prevState = current.memoizedState;
if (__DEV__) {
if (
!finishedWork.type.defaultProps &&
!('ref' in finishedWork.memoizedProps) &&
!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)) {
startEffectTimer();
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
callComponentDidUpdateInDEV,
finishedWork,
instance,
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} else {
try {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
recordEffectDuration(finishedWork);
} else {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
callComponentDidUpdateInDEV,
finishedWork,
instance,
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} else {
try {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
}
}
export function commitClassDidMount(finishedWork: Fiber) {
const instance = finishedWork.stateNode;
if (typeof instance.componentDidMount === 'function') {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
callComponentDidMountInDEV,
finishedWork,
instance,
);
} else {
try {
instance.componentDidMount();
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
}
export function commitClassCallbacks(finishedWork: Fiber) {
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
const instance = finishedWork.stateNode;
if (__DEV__) {
if (
!finishedWork.type.defaultProps &&
!('ref' in finishedWork.memoizedProps) &&
!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',
);
}
}
}
try {
if (__DEV__) {
runWithFiberInDEV(finishedWork, commitCallbacks, updateQueue, instance);
} else {
commitCallbacks(updateQueue, instance);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
export function commitClassHiddenCallbacks(finishedWork: Fiber) {
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
const instance = finishedWork.stateNode;
try {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
commitHiddenCallbacks,
updateQueue,
instance,
);
} else {
commitHiddenCallbacks(updateQueue, instance);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
export function commitRootCallbacks(finishedWork: Fiber) {
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 {
if (__DEV__) {
runWithFiberInDEV(finishedWork, commitCallbacks, updateQueue, instance);
} else {
commitCallbacks(updateQueue, instance);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
}
function callGetSnapshotBeforeUpdates(
instance: any,
prevProps: any,
prevState: any,
) {
return instance.getSnapshotBeforeUpdate(prevProps, prevState);
}
export function commitClassSnapshot(finishedWork: Fiber, current: Fiber) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
if (__DEV__) {
if (
!finishedWork.type.defaultProps &&
!('ref' in finishedWork.memoizedProps) &&
!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',
);
}
}
}
try {
const resolvedPrevProps = resolveClassComponentProps(
finishedWork.type,
prevProps,
finishedWork.elementType === finishedWork.type,
);
let snapshot;
if (__DEV__) {
snapshot = runWithFiberInDEV(
finishedWork,
callGetSnapshotBeforeUpdates,
instance,
resolvedPrevProps,
prevState,
);
const didWarnSet =
((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
runWithFiberInDEV(finishedWork, () => {
console.error(
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
'must be returned. You have returned undefined.',
getComponentNameFromFiber(finishedWork),
);
});
}
} else {
snapshot = callGetSnapshotBeforeUpdates(
instance,
resolvedPrevProps,
prevState,
);
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
export function safelyCallComponentWillUnmount(
current: Fiber,
nearestMountedAncestor: Fiber | null,
instance: any,
) {
instance.props = resolveClassComponentProps(
current.type,
current.memoizedProps,
current.elementType === current.type,
);
instance.state = current.memoizedState;
if (shouldProfile(current)) {
startEffectTimer();
if (__DEV__) {
runWithFiberInDEV(
current,
callComponentWillUnmountInDEV,
current,
nearestMountedAncestor,
instance,
);
} else {
try {
instance.componentWillUnmount();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}
recordEffectDuration(current);
} else {
if (__DEV__) {
runWithFiberInDEV(
current,
callComponentWillUnmountInDEV,
current,
nearestMountedAncestor,
instance,
);
} else {
try {
instance.componentWillUnmount();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}
}
}
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;
}
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
instanceToUse = instance;
}
if (typeof ref === 'function') {
if (shouldProfile(finishedWork)) {
try {
startEffectTimer();
finishedWork.refCleanup = ref(instanceToUse);
} finally {
recordEffectDuration(finishedWork);
}
} else {
finishedWork.refCleanup = ref(instanceToUse);
}
} else {
if (__DEV__) {
if (disableStringRefs && typeof ref === 'string') {
console.error('String refs are no longer supported.');
} else if (!ref.hasOwnProperty('current')) {
console.error(
'Unexpected ref object provided for %s. ' +
'Use either a ref-setter function or React.createRef().',
getComponentNameFromFiber(finishedWork),
);
}
}
ref.current = instanceToUse;
}
}
}
export function safelyAttachRef(
current: Fiber,
nearestMountedAncestor: Fiber | null,
) {
try {
if (__DEV__) {
runWithFiberInDEV(current, commitAttachRef, current);
} else {
commitAttachRef(current);
}
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}
export 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 {
startEffectTimer();
if (__DEV__) {
runWithFiberInDEV(current, refCleanup);
} else {
refCleanup();
}
} finally {
recordEffectDuration(current);
}
} else {
if (__DEV__) {
runWithFiberInDEV(current, refCleanup);
} else {
refCleanup();
}
}
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
} finally {
current.refCleanup = null;
const finishedWork = current.alternate;
if (finishedWork != null) {
finishedWork.refCleanup = null;
}
}
} else if (typeof ref === 'function') {
try {
if (shouldProfile(current)) {
try {
startEffectTimer();
if (__DEV__) {
(runWithFiberInDEV(current, ref, null): void);
} else {
ref(null);
}
} finally {
recordEffectDuration(current);
}
} else {
if (__DEV__) {
(runWithFiberInDEV(current, ref, null): void);
} else {
ref(null);
}
}
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
} else {
ref.current = null;
}
}
}
function safelyCallDestroy(
current: Fiber,
nearestMountedAncestor: Fiber | null,
destroy: () => void,
) {
if (__DEV__) {
runWithFiberInDEV(
current,
callDestroyInDEV,
current,
nearestMountedAncestor,
destroy,
);
} else {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}
}
function commitProfiler(
finishedWork: Fiber,
current: Fiber | null,
commitStartTime: number,
effectDuration: number,
) {
const {id, onCommit, onRender} = finishedWork.memoizedProps;
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}
}
if (typeof onRender === 'function') {
onRender(
id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitStartTime,
);
}
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitStartTime,
);
}
}
}
export function commitProfilerUpdate(
finishedWork: Fiber,
current: Fiber | null,
commitStartTime: number,
effectDuration: number,
) {
if (enableProfilerTimer) {
try {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
commitProfiler,
finishedWork,
current,
commitStartTime,
effectDuration,
);
} else {
commitProfiler(finishedWork, current, commitStartTime, effectDuration);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
function commitProfilerPostCommitImpl(
finishedWork: Fiber,
current: Fiber | null,
commitStartTime: number,
passiveEffectDuration: number,
): void {
const {id, onPostCommit} = finishedWork.memoizedProps;
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}
}
if (typeof onPostCommit === 'function') {
onPostCommit(id, phase, passiveEffectDuration, commitStartTime);
}
}
export function commitProfilerPostCommit(
finishedWork: Fiber,
current: Fiber | null,
commitStartTime: number,
passiveEffectDuration: number,
) {
try {
if (__DEV__) {
runWithFiberInDEV(
finishedWork,
commitProfilerPostCommitImpl,
finishedWork,
current,
commitStartTime,
passiveEffectDuration,
);
} else {
commitProfilerPostCommitImpl(
finishedWork,
current,
commitStartTime,
passiveEffectDuration,
);
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}