diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
index 59ed975cd4..5e843a70d8 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
@@ -27,7 +27,6 @@ import type {OffscreenState} from './ReactFiberOffscreenComponent';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {
- deferPassiveEffectCleanupDuringUnmount,
enableSchedulerTracing,
enableProfilerTimer,
enableProfilerCommitHooks,
@@ -36,7 +35,6 @@ import {
enableFundamentalAPI,
enableSuspenseCallback,
enableScopeAPI,
- runAllPassiveEffectDestroysBeforeCreates,
enableCreateEventHandleAPI,
} from 'shared/ReactFeatureFlags';
import {
@@ -71,7 +69,6 @@ import {
Placement,
Snapshot,
Update,
- Passive,
} from './ReactSideEffectTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
@@ -81,9 +78,7 @@ import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
import {
getCommitTime,
recordLayoutEffectDuration,
- recordPassiveEffectDuration,
startLayoutEffectTimer,
- startPassiveEffectTimer,
} from './ReactProfilerTimer.new';
import {ProfileMode} from './ReactTypeOfMode';
import {commitUpdateQueue} from './ReactUpdateQueue.new';
@@ -134,10 +129,6 @@ import {
Passive as HookPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
-import {
- runWithPriority,
- NormalPriority,
-} from './SchedulerWithReactIntegration.new';
import {
updateDeprecatedEventListeners,
unmountDeprecatedResponderListeners,
@@ -394,67 +385,22 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
}
function schedulePassiveEffects(finishedWork: Fiber) {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- 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 {
- const {next, tag} = effect;
- if (
- (tag & HookPassive) !== NoHookEffect &&
- (tag & HookHasEffect) !== NoHookEffect
- ) {
- enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
- enqueuePendingPassiveHookEffectMount(finishedWork, effect);
- }
- effect = next;
- } while (effect !== firstEffect);
- }
- }
-}
-
-export function commitPassiveHookEffects(finishedWork: Fiber): void {
- if ((finishedWork.effectTag & Passive) !== NoEffect) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- case Block: {
- // TODO (#17945) We should call all passive destroy functions (for all fibers)
- // before calling any create functions. The current approach only serializes
- // these for a single fiber.
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- finishedWork.mode & ProfileMode
- ) {
- try {
- startPassiveEffectTimer();
- commitHookEffectListUnmount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- commitHookEffectListMount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- } finally {
- recordPassiveEffectDuration(finishedWork);
- }
- } else {
- commitHookEffectListUnmount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
- }
- break;
+ 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 {
+ const {next, tag} = effect;
+ if (
+ (tag & HookPassive) !== NoHookEffect &&
+ (tag & HookHasEffect) !== NoHookEffect
+ ) {
+ enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
+ enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
- default:
- break;
- }
+ effect = next;
+ } while (effect !== firstEffect);
}
}
@@ -543,9 +489,7 @@ function commitLifeCycles(
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
- if (runAllPassiveEffectDestroysBeforeCreates) {
- schedulePassiveEffects(finishedWork);
- }
+ schedulePassiveEffects(finishedWork);
return;
}
case ClassComponent: {
@@ -946,74 +890,28 @@ function commitUnmount(
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- let effect = firstEffect;
- do {
- const {destroy, tag} = effect;
- if (destroy !== undefined) {
- if ((tag & HookPassive) !== NoHookEffect) {
- enqueuePendingPassiveHookEffectUnmount(current, effect);
+ let effect = firstEffect;
+ do {
+ const {destroy, tag} = effect;
+ if (destroy !== undefined) {
+ if ((tag & HookPassive) !== NoHookEffect) {
+ enqueuePendingPassiveHookEffectUnmount(current, effect);
+ } else {
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ current.mode & ProfileMode
+ ) {
+ startLayoutEffectTimer();
+ safelyCallDestroy(current, destroy);
+ recordLayoutEffectDuration(current);
} else {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- current.mode & ProfileMode
- ) {
- startLayoutEffectTimer();
- safelyCallDestroy(current, destroy);
- recordLayoutEffectDuration(current);
- } else {
- safelyCallDestroy(current, destroy);
- }
+ safelyCallDestroy(current, destroy);
}
}
- effect = effect.next;
- } while (effect !== firstEffect);
- } else {
- // When the owner fiber is deleted, the destroy function of a passive
- // effect hook is called during the synchronous commit phase. This is
- // a concession to implementation complexity. Calling it in the
- // passive effect phase (like they usually are, when dependencies
- // change during an update) would require either traversing the
- // children of the deleted fiber again, or including unmount effects
- // as part of the fiber effect list.
- //
- // Because this is during the sync commit phase, we need to change
- // the priority.
- //
- // TODO: Reconsider this implementation trade off.
- const priorityLevel =
- renderPriorityLevel > NormalPriority
- ? NormalPriority
- : renderPriorityLevel;
- runWithPriority(priorityLevel, () => {
- let effect = firstEffect;
- do {
- const {destroy, tag} = effect;
- if (destroy !== undefined) {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- current.mode & ProfileMode
- ) {
- if ((tag & HookPassive) !== NoHookEffect) {
- safelyCallDestroy(current, destroy);
- } else {
- startLayoutEffectTimer();
- safelyCallDestroy(current, destroy);
- recordLayoutEffectDuration(current);
- }
- } else {
- safelyCallDestroy(current, destroy);
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- });
- }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
}
}
return;
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js
index 5017ed66fb..229eab40ee 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js
@@ -26,7 +26,6 @@ import type {ReactPriorityLevel} from './ReactInternalTypes';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {
- deferPassiveEffectCleanupDuringUnmount,
enableSchedulerTracing,
enableProfilerTimer,
enableProfilerCommitHooks,
@@ -35,7 +34,6 @@ import {
enableFundamentalAPI,
enableSuspenseCallback,
enableScopeAPI,
- runAllPassiveEffectDestroysBeforeCreates,
enableCreateEventHandleAPI,
} from 'shared/ReactFeatureFlags';
import {
@@ -68,7 +66,6 @@ import {
Placement,
Snapshot,
Update,
- Passive,
} from './ReactSideEffectTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
@@ -78,9 +75,7 @@ import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
import {
getCommitTime,
recordLayoutEffectDuration,
- recordPassiveEffectDuration,
startLayoutEffectTimer,
- startPassiveEffectTimer,
} from './ReactProfilerTimer.old';
import {ProfileMode} from './ReactTypeOfMode';
import {commitUpdateQueue} from './ReactUpdateQueue.old';
@@ -131,10 +126,6 @@ import {
Passive as HookPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
-import {
- runWithPriority,
- NormalPriority,
-} from './SchedulerWithReactIntegration.old';
import {
updateDeprecatedEventListeners,
unmountDeprecatedResponderListeners,
@@ -391,67 +382,22 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
}
function schedulePassiveEffects(finishedWork: Fiber) {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- 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 {
- const {next, tag} = effect;
- if (
- (tag & HookPassive) !== NoHookEffect &&
- (tag & HookHasEffect) !== NoHookEffect
- ) {
- enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
- enqueuePendingPassiveHookEffectMount(finishedWork, effect);
- }
- effect = next;
- } while (effect !== firstEffect);
- }
- }
-}
-
-export function commitPassiveHookEffects(finishedWork: Fiber): void {
- if ((finishedWork.effectTag & Passive) !== NoEffect) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- case Block: {
- // TODO (#17945) We should call all passive destroy functions (for all fibers)
- // before calling any create functions. The current approach only serializes
- // these for a single fiber.
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- finishedWork.mode & ProfileMode
- ) {
- try {
- startPassiveEffectTimer();
- commitHookEffectListUnmount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- commitHookEffectListMount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- } finally {
- recordPassiveEffectDuration(finishedWork);
- }
- } else {
- commitHookEffectListUnmount(
- HookPassive | HookHasEffect,
- finishedWork,
- );
- commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
- }
- break;
+ 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 {
+ const {next, tag} = effect;
+ if (
+ (tag & HookPassive) !== NoHookEffect &&
+ (tag & HookHasEffect) !== NoHookEffect
+ ) {
+ enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
+ enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
- default:
- break;
- }
+ effect = next;
+ } while (effect !== firstEffect);
}
}
@@ -540,9 +486,7 @@ function commitLifeCycles(
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
- if (runAllPassiveEffectDestroysBeforeCreates) {
- schedulePassiveEffects(finishedWork);
- }
+ schedulePassiveEffects(finishedWork);
return;
}
case ClassComponent: {
@@ -944,74 +888,28 @@ function commitUnmount(
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- let effect = firstEffect;
- do {
- const {destroy, tag} = effect;
- if (destroy !== undefined) {
- if ((tag & HookPassive) !== NoHookEffect) {
- enqueuePendingPassiveHookEffectUnmount(current, effect);
+ let effect = firstEffect;
+ do {
+ const {destroy, tag} = effect;
+ if (destroy !== undefined) {
+ if ((tag & HookPassive) !== NoHookEffect) {
+ enqueuePendingPassiveHookEffectUnmount(current, effect);
+ } else {
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ current.mode & ProfileMode
+ ) {
+ startLayoutEffectTimer();
+ safelyCallDestroy(current, destroy);
+ recordLayoutEffectDuration(current);
} else {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- current.mode & ProfileMode
- ) {
- startLayoutEffectTimer();
- safelyCallDestroy(current, destroy);
- recordLayoutEffectDuration(current);
- } else {
- safelyCallDestroy(current, destroy);
- }
+ safelyCallDestroy(current, destroy);
}
}
- effect = effect.next;
- } while (effect !== firstEffect);
- } else {
- // When the owner fiber is deleted, the destroy function of a passive
- // effect hook is called during the synchronous commit phase. This is
- // a concession to implementation complexity. Calling it in the
- // passive effect phase (like they usually are, when dependencies
- // change during an update) would require either traversing the
- // children of the deleted fiber again, or including unmount effects
- // as part of the fiber effect list.
- //
- // Because this is during the sync commit phase, we need to change
- // the priority.
- //
- // TODO: Reconsider this implementation trade off.
- const priorityLevel =
- renderPriorityLevel > NormalPriority
- ? NormalPriority
- : renderPriorityLevel;
- runWithPriority(priorityLevel, () => {
- let effect = firstEffect;
- do {
- const {destroy, tag} = effect;
- if (destroy !== undefined) {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- current.mode & ProfileMode
- ) {
- if ((tag & HookPassive) !== NoHookEffect) {
- safelyCallDestroy(current, destroy);
- } else {
- startLayoutEffectTimer();
- safelyCallDestroy(current, destroy);
- recordLayoutEffectDuration(current);
- }
- } else {
- safelyCallDestroy(current, destroy);
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- });
- }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
}
}
return;
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index 438c93cdcf..39ddbe53f8 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -19,8 +19,6 @@ import type {StackCursor} from './ReactFiberStack.new';
import {
warnAboutDeprecatedLifecycles,
- deferPassiveEffectCleanupDuringUnmount,
- runAllPassiveEffectDestroysBeforeCreates,
enableSuspenseServerRenderer,
replayFailedUnitOfWorkWithInvokeGuardedCallback,
enableProfilerTimer,
@@ -154,7 +152,6 @@ import {
import {
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
commitLifeCycles as commitLayoutEffectOnFiber,
- commitPassiveHookEffects,
commitPlacement,
commitWork,
commitDeletion,
@@ -2260,15 +2257,13 @@ export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber,
effect: HookEffect,
): void {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- pendingPassiveHookEffectsMount.push(effect, fiber);
- if (!rootDoesHavePassiveEffects) {
- rootDoesHavePassiveEffects = true;
- scheduleCallback(NormalSchedulerPriority, () => {
- flushPassiveEffects();
- return null;
- });
- }
+ pendingPassiveHookEffectsMount.push(effect, fiber);
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback(NormalSchedulerPriority, () => {
+ flushPassiveEffects();
+ return null;
+ });
}
}
@@ -2276,25 +2271,21 @@ export function enqueuePendingPassiveHookEffectUnmount(
fiber: Fiber,
effect: HookEffect,
): void {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- pendingPassiveHookEffectsUnmount.push(effect, fiber);
- if (__DEV__) {
- if (deferPassiveEffectCleanupDuringUnmount) {
- fiber.effectTag |= PassiveUnmountPendingDev;
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.effectTag |= PassiveUnmountPendingDev;
- }
- }
- }
- if (!rootDoesHavePassiveEffects) {
- rootDoesHavePassiveEffects = true;
- scheduleCallback(NormalSchedulerPriority, () => {
- flushPassiveEffects();
- return null;
- });
+ pendingPassiveHookEffectsUnmount.push(effect, fiber);
+ if (__DEV__) {
+ fiber.effectTag |= PassiveUnmountPendingDev;
+ const alternate = fiber.alternate;
+ if (alternate !== null) {
+ alternate.effectTag |= PassiveUnmountPendingDev;
}
}
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback(NormalSchedulerPriority, () => {
+ flushPassiveEffects();
+ return null;
+ });
+ }
}
function invokePassiveEffectCreate(effect: HookEffect): void {
@@ -2325,82 +2316,31 @@ function flushPassiveEffectsImpl() {
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
- if (runAllPassiveEffectDestroysBeforeCreates) {
- // It's important that ALL pending passive effect destroy functions are called
- // before ANY passive effect create functions are called.
- // Otherwise effects in sibling components might interfere with each other.
- // e.g. a destroy function in one component may unintentionally override a ref
- // value set by a create function in another component.
- // Layout effects have the same constraint.
+ // It's important that ALL pending passive effect destroy functions are called
+ // before ANY passive effect create functions are called.
+ // Otherwise effects in sibling components might interfere with each other.
+ // e.g. a destroy function in one component may unintentionally override a ref
+ // value set by a create function in another component.
+ // Layout effects have the same constraint.
- // First pass: Destroy stale passive effects.
- const unmountEffects = pendingPassiveHookEffectsUnmount;
- pendingPassiveHookEffectsUnmount = [];
- for (let i = 0; i < unmountEffects.length; i += 2) {
- const effect = ((unmountEffects[i]: any): HookEffect);
- const fiber = ((unmountEffects[i + 1]: any): Fiber);
- const destroy = effect.destroy;
- effect.destroy = undefined;
+ // First pass: Destroy stale passive effects.
+ const unmountEffects = pendingPassiveHookEffectsUnmount;
+ pendingPassiveHookEffectsUnmount = [];
+ for (let i = 0; i < unmountEffects.length; i += 2) {
+ const effect = ((unmountEffects[i]: any): HookEffect);
+ const fiber = ((unmountEffects[i + 1]: any): Fiber);
+ const destroy = effect.destroy;
+ effect.destroy = undefined;
- if (__DEV__) {
- if (deferPassiveEffectCleanupDuringUnmount) {
- fiber.effectTag &= ~PassiveUnmountPendingDev;
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.effectTag &= ~PassiveUnmountPendingDev;
- }
- }
- }
-
- if (typeof destroy === 'function') {
- if (__DEV__) {
- setCurrentDebugFiberInDEV(fiber);
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- fiber.mode & ProfileMode
- ) {
- startPassiveEffectTimer();
- invokeGuardedCallback(null, destroy, null);
- recordPassiveEffectDuration(fiber);
- } else {
- invokeGuardedCallback(null, destroy, null);
- }
- if (hasCaughtError()) {
- invariant(fiber !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(fiber, error);
- }
- resetCurrentDebugFiberInDEV();
- } else {
- try {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- fiber.mode & ProfileMode
- ) {
- try {
- startPassiveEffectTimer();
- destroy();
- } finally {
- recordPassiveEffectDuration(fiber);
- }
- } else {
- destroy();
- }
- } catch (error) {
- invariant(fiber !== null, 'Should be working on an effect.');
- captureCommitPhaseError(fiber, error);
- }
- }
+ if (__DEV__) {
+ fiber.effectTag &= ~PassiveUnmountPendingDev;
+ const alternate = fiber.alternate;
+ if (alternate !== null) {
+ alternate.effectTag &= ~PassiveUnmountPendingDev;
}
}
- // Second pass: Create new passive effects.
- const mountEffects = pendingPassiveHookEffectsMount;
- pendingPassiveHookEffectsMount = [];
- for (let i = 0; i < mountEffects.length; i += 2) {
- const effect = ((mountEffects[i]: any): HookEffect);
- const fiber = ((mountEffects[i + 1]: any): Fiber);
+
+ if (typeof destroy === 'function') {
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
@@ -2409,10 +2349,10 @@ function flushPassiveEffectsImpl() {
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
- invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ invokeGuardedCallback(null, destroy, null);
recordPassiveEffectDuration(fiber);
} else {
- invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ invokeGuardedCallback(null, destroy, null);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
@@ -2422,7 +2362,6 @@ function flushPassiveEffectsImpl() {
resetCurrentDebugFiberInDEV();
} else {
try {
- const create = effect.create;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
@@ -2430,12 +2369,12 @@ function flushPassiveEffectsImpl() {
) {
try {
startPassiveEffectTimer();
- effect.destroy = create();
+ destroy();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
- effect.destroy = create();
+ destroy();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
@@ -2444,33 +2383,60 @@ function flushPassiveEffectsImpl() {
}
}
}
+ // Second pass: Create new passive effects.
+ const mountEffects = pendingPassiveHookEffectsMount;
+ pendingPassiveHookEffectsMount = [];
+ for (let i = 0; i < mountEffects.length; i += 2) {
+ const effect = ((mountEffects[i]: any): HookEffect);
+ const fiber = ((mountEffects[i + 1]: any): Fiber);
+ if (__DEV__) {
+ setCurrentDebugFiberInDEV(fiber);
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ fiber.mode & ProfileMode
+ ) {
+ startPassiveEffectTimer();
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ recordPassiveEffectDuration(fiber);
+ } else {
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ }
+ if (hasCaughtError()) {
+ invariant(fiber !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(fiber, error);
+ }
+ resetCurrentDebugFiberInDEV();
+ } else {
+ try {
+ const create = effect.create;
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ fiber.mode & ProfileMode
+ ) {
+ try {
+ startPassiveEffectTimer();
+ effect.destroy = create();
+ } finally {
+ recordPassiveEffectDuration(fiber);
+ }
+ } else {
+ effect.destroy = create();
+ }
+ } catch (error) {
+ invariant(fiber !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(fiber, error);
+ }
+ }
+ }
+
// Note: This currently assumes there are no passive effects on the root fiber
// because the root is not part of its own effect list.
// This could change in the future.
let effect = root.current.firstEffect;
while (effect !== null) {
- // We do this work above if this flag is enabled, so we shouldn't be
- // doing it here.
- if (!runAllPassiveEffectDestroysBeforeCreates) {
- if (__DEV__) {
- setCurrentDebugFiberInDEV(effect);
- invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
- if (hasCaughtError()) {
- invariant(effect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(effect, error);
- }
- resetCurrentDebugFiberInDEV();
- } else {
- try {
- commitPassiveHookEffects(effect);
- } catch (error) {
- invariant(effect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(effect, error);
- }
- }
- }
-
const nextNextEffect = effect.nextEffect;
// Remove nextEffect pointer to assist GC
effect.nextEffect = null;
@@ -2868,15 +2834,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
return;
}
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- // If there are pending passive effects unmounts for this Fiber,
- // we can assume that they would have prevented this update.
- if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
- return;
- }
+ // If there are pending passive effects unmounts for this Fiber,
+ // we can assume that they would have prevented this update.
+ if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
+ return;
}
// We show the whole stack but dedupe on the top component's name because
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index 24bee14eb5..daaaced567 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -18,8 +18,6 @@ import type {Effect as HookEffect} from './ReactFiberHooks.old';
import {
warnAboutDeprecatedLifecycles,
- deferPassiveEffectCleanupDuringUnmount,
- runAllPassiveEffectDestroysBeforeCreates,
enableSuspenseServerRenderer,
replayFailedUnitOfWorkWithInvokeGuardedCallback,
enableProfilerTimer,
@@ -151,7 +149,6 @@ import {
import {
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
commitLifeCycles as commitLayoutEffectOnFiber,
- commitPassiveHookEffects,
commitPlacement,
commitWork,
commitDeletion,
@@ -2400,15 +2397,13 @@ export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber,
effect: HookEffect,
): void {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- pendingPassiveHookEffectsMount.push(effect, fiber);
- if (!rootDoesHavePassiveEffects) {
- rootDoesHavePassiveEffects = true;
- scheduleCallback(NormalPriority, () => {
- flushPassiveEffects();
- return null;
- });
- }
+ pendingPassiveHookEffectsMount.push(effect, fiber);
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback(NormalPriority, () => {
+ flushPassiveEffects();
+ return null;
+ });
}
}
@@ -2416,25 +2411,21 @@ export function enqueuePendingPassiveHookEffectUnmount(
fiber: Fiber,
effect: HookEffect,
): void {
- if (runAllPassiveEffectDestroysBeforeCreates) {
- pendingPassiveHookEffectsUnmount.push(effect, fiber);
- if (__DEV__) {
- if (deferPassiveEffectCleanupDuringUnmount) {
- fiber.effectTag |= PassiveUnmountPendingDev;
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.effectTag |= PassiveUnmountPendingDev;
- }
- }
- }
- if (!rootDoesHavePassiveEffects) {
- rootDoesHavePassiveEffects = true;
- scheduleCallback(NormalPriority, () => {
- flushPassiveEffects();
- return null;
- });
+ pendingPassiveHookEffectsUnmount.push(effect, fiber);
+ if (__DEV__) {
+ fiber.effectTag |= PassiveUnmountPendingDev;
+ const alternate = fiber.alternate;
+ if (alternate !== null) {
+ alternate.effectTag |= PassiveUnmountPendingDev;
}
}
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback(NormalPriority, () => {
+ flushPassiveEffects();
+ return null;
+ });
+ }
}
function invokePassiveEffectCreate(effect: HookEffect): void {
@@ -2473,82 +2464,31 @@ function flushPassiveEffectsImpl() {
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
- if (runAllPassiveEffectDestroysBeforeCreates) {
- // It's important that ALL pending passive effect destroy functions are called
- // before ANY passive effect create functions are called.
- // Otherwise effects in sibling components might interfere with each other.
- // e.g. a destroy function in one component may unintentionally override a ref
- // value set by a create function in another component.
- // Layout effects have the same constraint.
+ // It's important that ALL pending passive effect destroy functions are called
+ // before ANY passive effect create functions are called.
+ // Otherwise effects in sibling components might interfere with each other.
+ // e.g. a destroy function in one component may unintentionally override a ref
+ // value set by a create function in another component.
+ // Layout effects have the same constraint.
- // First pass: Destroy stale passive effects.
- const unmountEffects = pendingPassiveHookEffectsUnmount;
- pendingPassiveHookEffectsUnmount = [];
- for (let i = 0; i < unmountEffects.length; i += 2) {
- const effect = ((unmountEffects[i]: any): HookEffect);
- const fiber = ((unmountEffects[i + 1]: any): Fiber);
- const destroy = effect.destroy;
- effect.destroy = undefined;
+ // First pass: Destroy stale passive effects.
+ const unmountEffects = pendingPassiveHookEffectsUnmount;
+ pendingPassiveHookEffectsUnmount = [];
+ for (let i = 0; i < unmountEffects.length; i += 2) {
+ const effect = ((unmountEffects[i]: any): HookEffect);
+ const fiber = ((unmountEffects[i + 1]: any): Fiber);
+ const destroy = effect.destroy;
+ effect.destroy = undefined;
- if (__DEV__) {
- if (deferPassiveEffectCleanupDuringUnmount) {
- fiber.effectTag &= ~PassiveUnmountPendingDev;
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.effectTag &= ~PassiveUnmountPendingDev;
- }
- }
- }
-
- if (typeof destroy === 'function') {
- if (__DEV__) {
- setCurrentDebugFiberInDEV(fiber);
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- fiber.mode & ProfileMode
- ) {
- startPassiveEffectTimer();
- invokeGuardedCallback(null, destroy, null);
- recordPassiveEffectDuration(fiber);
- } else {
- invokeGuardedCallback(null, destroy, null);
- }
- if (hasCaughtError()) {
- invariant(fiber !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(fiber, error);
- }
- resetCurrentDebugFiberInDEV();
- } else {
- try {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- fiber.mode & ProfileMode
- ) {
- try {
- startPassiveEffectTimer();
- destroy();
- } finally {
- recordPassiveEffectDuration(fiber);
- }
- } else {
- destroy();
- }
- } catch (error) {
- invariant(fiber !== null, 'Should be working on an effect.');
- captureCommitPhaseError(fiber, error);
- }
- }
+ if (__DEV__) {
+ fiber.effectTag &= ~PassiveUnmountPendingDev;
+ const alternate = fiber.alternate;
+ if (alternate !== null) {
+ alternate.effectTag &= ~PassiveUnmountPendingDev;
}
}
- // Second pass: Create new passive effects.
- const mountEffects = pendingPassiveHookEffectsMount;
- pendingPassiveHookEffectsMount = [];
- for (let i = 0; i < mountEffects.length; i += 2) {
- const effect = ((mountEffects[i]: any): HookEffect);
- const fiber = ((mountEffects[i + 1]: any): Fiber);
+
+ if (typeof destroy === 'function') {
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
@@ -2557,10 +2497,10 @@ function flushPassiveEffectsImpl() {
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
- invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ invokeGuardedCallback(null, destroy, null);
recordPassiveEffectDuration(fiber);
} else {
- invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ invokeGuardedCallback(null, destroy, null);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
@@ -2570,7 +2510,6 @@ function flushPassiveEffectsImpl() {
resetCurrentDebugFiberInDEV();
} else {
try {
- const create = effect.create;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
@@ -2578,12 +2517,12 @@ function flushPassiveEffectsImpl() {
) {
try {
startPassiveEffectTimer();
- effect.destroy = create();
+ destroy();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
- effect.destroy = create();
+ destroy();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
@@ -2592,33 +2531,60 @@ function flushPassiveEffectsImpl() {
}
}
}
+ // Second pass: Create new passive effects.
+ const mountEffects = pendingPassiveHookEffectsMount;
+ pendingPassiveHookEffectsMount = [];
+ for (let i = 0; i < mountEffects.length; i += 2) {
+ const effect = ((mountEffects[i]: any): HookEffect);
+ const fiber = ((mountEffects[i + 1]: any): Fiber);
+ if (__DEV__) {
+ setCurrentDebugFiberInDEV(fiber);
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ fiber.mode & ProfileMode
+ ) {
+ startPassiveEffectTimer();
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ recordPassiveEffectDuration(fiber);
+ } else {
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
+ }
+ if (hasCaughtError()) {
+ invariant(fiber !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(fiber, error);
+ }
+ resetCurrentDebugFiberInDEV();
+ } else {
+ try {
+ const create = effect.create;
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ fiber.mode & ProfileMode
+ ) {
+ try {
+ startPassiveEffectTimer();
+ effect.destroy = create();
+ } finally {
+ recordPassiveEffectDuration(fiber);
+ }
+ } else {
+ effect.destroy = create();
+ }
+ } catch (error) {
+ invariant(fiber !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(fiber, error);
+ }
+ }
+ }
+
// Note: This currently assumes there are no passive effects on the root fiber
// because the root is not part of its own effect list.
// This could change in the future.
let effect = root.current.firstEffect;
while (effect !== null) {
- // We do this work above if this flag is enabled, so we shouldn't be
- // doing it here.
- if (!runAllPassiveEffectDestroysBeforeCreates) {
- if (__DEV__) {
- setCurrentDebugFiberInDEV(effect);
- invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
- if (hasCaughtError()) {
- invariant(effect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(effect, error);
- }
- resetCurrentDebugFiberInDEV();
- } else {
- try {
- commitPassiveHookEffects(effect);
- } catch (error) {
- invariant(effect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(effect, error);
- }
- }
- }
-
const nextNextEffect = effect.nextEffect;
// Remove nextEffect pointer to assist GC
effect.nextEffect = null;
@@ -3037,15 +3003,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
return;
}
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- // If there are pending passive effects unmounts for this Fiber,
- // we can assume that they would have prevented this update.
- if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
- return;
- }
+ // If there are pending passive effects unmounts for this Fiber,
+ // we can assume that they would have prevented this update.
+ if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
+ return;
}
// We show the whole stack but dedupe on the top component's name because
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index 5e34e34646..8cf6e29a35 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -15,7 +15,6 @@
let React;
let ReactCache;
let TextResource;
-let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let SchedulerTracing;
@@ -33,21 +32,12 @@ let useDeferredValue;
let forwardRef;
let memo;
let act;
-let deferPassiveEffectCleanupDuringUnmount;
-let runAllPassiveEffectDestroysBeforeCreates;
describe('ReactHooksWithNoopRenderer', () => {
beforeEach(() => {
jest.resetModules();
jest.useFakeTimers();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
-
- deferPassiveEffectCleanupDuringUnmount =
- ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount;
- runAllPassiveEffectDestroysBeforeCreates =
- ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates;
-
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
@@ -1119,7 +1109,6 @@ describe('ReactHooksWithNoopRenderer', () => {
},
);
- // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('defers passive effect destroy functions during unmount', () => {
function Child({bar, foo}) {
React.useEffect(() => {
@@ -1204,7 +1193,6 @@ describe('ReactHooksWithNoopRenderer', () => {
});
});
- // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
let completePendingRequest = null;
function Component() {
@@ -1252,7 +1240,6 @@ describe('ReactHooksWithNoopRenderer', () => {
});
});
- // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('does not warn about state updates for unmounted components with pending passive unmounts for alternates', () => {
let setParentState = null;
const setChildStates = [];
@@ -1378,7 +1365,6 @@ describe('ReactHooksWithNoopRenderer', () => {
});
});
- // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('still warns if there are pending passive unmount effects but not for the current fiber', () => {
let completePendingRequest = null;
function ComponentWithXHR() {
@@ -2085,80 +2071,66 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
});
- if (runAllPassiveEffectDestroysBeforeCreates) {
- it('unmounts all previous effects between siblings before creating any new ones', () => {
- function Counter({count, label}) {
- useEffect(() => {
- Scheduler.unstable_yieldValue(`Mount ${label} [${count}]`);
- return () => {
- Scheduler.unstable_yieldValue(`Unmount ${label} [${count}]`);
- };
- });
- return ;
- }
- act(() => {
- ReactNoop.render(
- <>
-
-
- >,
- () => Scheduler.unstable_yieldValue('Sync effect'),
- );
- expect(Scheduler).toFlushAndYieldThrough([
- 'A 0',
- 'B 0',
- 'Sync effect',
- ]);
- expect(ReactNoop.getChildren()).toEqual([span('A 0'), span('B 0')]);
+ it('unmounts all previous effects between siblings before creating any new ones', () => {
+ function Counter({count, label}) {
+ useEffect(() => {
+ Scheduler.unstable_yieldValue(`Mount ${label} [${count}]`);
+ return () => {
+ Scheduler.unstable_yieldValue(`Unmount ${label} [${count}]`);
+ };
});
-
- expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
-
- act(() => {
- ReactNoop.render(
- <>
-
-
- >,
- () => Scheduler.unstable_yieldValue('Sync effect'),
- );
- expect(Scheduler).toFlushAndYieldThrough([
- 'A 1',
- 'B 1',
- 'Sync effect',
- ]);
- expect(ReactNoop.getChildren()).toEqual([span('A 1'), span('B 1')]);
- });
- expect(Scheduler).toHaveYielded([
- 'Unmount A [0]',
- 'Unmount B [0]',
- 'Mount A [1]',
- 'Mount B [1]',
- ]);
-
- act(() => {
- ReactNoop.render(
- <>
-
-
- >,
- () => Scheduler.unstable_yieldValue('Sync effect'),
- );
- expect(Scheduler).toFlushAndYieldThrough([
- 'B 2',
- 'C 0',
- 'Sync effect',
- ]);
- expect(ReactNoop.getChildren()).toEqual([span('B 2'), span('C 0')]);
- });
- expect(Scheduler).toHaveYielded([
- 'Unmount A [1]',
- 'Unmount B [1]',
- 'Mount B [2]',
- 'Mount C [0]',
- ]);
+ return ;
+ }
+ act(() => {
+ ReactNoop.render(
+ <>
+
+
+ >,
+ () => Scheduler.unstable_yieldValue('Sync effect'),
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['A 0', 'B 0', 'Sync effect']);
+ expect(ReactNoop.getChildren()).toEqual([span('A 0'), span('B 0')]);
});
- }
+
+ expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
+
+ act(() => {
+ ReactNoop.render(
+ <>
+
+
+ >,
+ () => Scheduler.unstable_yieldValue('Sync effect'),
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['A 1', 'B 1', 'Sync effect']);
+ expect(ReactNoop.getChildren()).toEqual([span('A 1'), span('B 1')]);
+ });
+ expect(Scheduler).toHaveYielded([
+ 'Unmount A [0]',
+ 'Unmount B [0]',
+ 'Mount A [1]',
+ 'Mount B [1]',
+ ]);
+
+ act(() => {
+ ReactNoop.render(
+ <>
+
+
+ >,
+ () => Scheduler.unstable_yieldValue('Sync effect'),
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['B 2', 'C 0', 'Sync effect']);
+ expect(ReactNoop.getChildren()).toEqual([span('B 2'), span('C 0')]);
+ });
+ expect(Scheduler).toHaveYielded([
+ 'Unmount A [1]',
+ 'Unmount B [1]',
+ 'Mount B [2]',
+ 'Mount C [0]',
+ ]);
+ });
it('handles errors in create on mount', () => {
function Counter(props) {
@@ -2236,30 +2208,19 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
- expect(Scheduler).toHaveYielded(
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ? ['Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Oops!']
- : [
- 'Unmount A [0]',
- 'Unmount B [0]',
- 'Mount A [1]',
- 'Oops!',
- 'Unmount A [1]',
- ],
- );
+ expect(Scheduler).toHaveYielded([
+ 'Unmount A [0]',
+ 'Unmount B [0]',
+ 'Mount A [1]',
+ 'Oops!',
+ ]);
expect(ReactNoop.getChildren()).toEqual([]);
});
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- expect(Scheduler).toHaveYielded([
- // Clean up effect A runs passively on unmount.
- // There's no effect B to clean-up, because it never mounted.
- 'Unmount A [1]',
- ]);
- }
+ expect(Scheduler).toHaveYielded([
+ // Clean up effect A runs passively on unmount.
+ // There's no effect B to clean-up, because it never mounted.
+ 'Unmount A [1]',
+ ]);
});
it('handles errors in destroy on update', () => {
@@ -2292,49 +2253,32 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
});
- if (
- deferPassiveEffectCleanupDuringUnmount &&
- runAllPassiveEffectDestroysBeforeCreates
- ) {
- act(() => {
- // This update will trigger an error during passive effect unmount
- ReactNoop.render(, () =>
- Scheduler.unstable_yieldValue('Sync effect'),
- );
- expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
- expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
- expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
+ act(() => {
+ // This update will trigger an error during passive effect unmount
+ ReactNoop.render(, () =>
+ Scheduler.unstable_yieldValue('Sync effect'),
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
+ expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
- // This branch enables a feature flag that flushes all passive destroys in a
- // separate pass before flushing any passive creates.
- // A result of this two-pass flush is that an error thrown from unmount does
- // not block the subsequent create functions from being run.
- expect(Scheduler).toHaveYielded([
- 'Oops!',
- 'Unmount B [0]',
- 'Mount A [1]',
- 'Mount B [1]',
- ]);
- });
+ // This branch enables a feature flag that flushes all passive destroys in a
+ // separate pass before flushing any passive creates.
+ // A result of this two-pass flush is that an error thrown from unmount does
+ // not block the subsequent create functions from being run.
+ expect(Scheduler).toHaveYielded([
+ 'Oops!',
+ 'Unmount B [0]',
+ 'Mount A [1]',
+ 'Mount B [1]',
+ ]);
+ });
- // gets unmounted because an error is thrown above.
- // The remaining destroy functions are run later on unmount, since they're passive.
- // In this case, one of them throws again (because of how the test is written).
- expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']);
- expect(ReactNoop.getChildren()).toEqual([]);
- } else {
- act(() => {
- // This update will trigger an error during passive effect unmount
- ReactNoop.render(, () =>
- Scheduler.unstable_yieldValue('Sync effect'),
- );
- expect(() => {
- expect(Scheduler).toFlushAndYield(['Count: 1', 'Sync effect']);
- }).toThrow('Oops!');
- expect(ReactNoop.getChildren()).toEqual([]);
- ReactNoop.flushPassiveEffects();
- });
- }
+ // gets unmounted because an error is thrown above.
+ // The remaining destroy functions are run later on unmount, since they're passive.
+ // In this case, one of them throws again (because of how the test is written).
+ expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']);
+ expect(ReactNoop.getChildren()).toEqual([]);
});
it('works with memo', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index 2e8689027e..ea0df588a9 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -1,5 +1,4 @@
let React;
-let ReactFeatureFlags;
let Fragment;
let ReactNoop;
let Scheduler;
@@ -14,7 +13,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
beforeEach(() => {
jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
React = require('react');
Fragment = React.Fragment;
ReactNoop = require('react-noop-renderer');
@@ -1737,26 +1735,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- if (
- ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
- ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
- ) {
- expect(Scheduler).toFlushAndYield([
- 'B',
- 'Destroy Layout Effect [Loading...]',
- 'Layout Effect [B]',
- 'Destroy Effect [Loading...]',
- 'Effect [B]',
- ]);
- } else {
- expect(Scheduler).toFlushAndYield([
- 'B',
- 'Destroy Layout Effect [Loading...]',
- 'Destroy Effect [Loading...]',
- 'Layout Effect [B]',
- 'Effect [B]',
- ]);
- }
+ expect(Scheduler).toFlushAndYield([
+ 'B',
+ 'Destroy Layout Effect [Loading...]',
+ 'Layout Effect [B]',
+ 'Destroy Effect [Loading...]',
+ 'Effect [B]',
+ ]);
// Update
ReactNoop.renderLegacySyncRoot(, () =>
@@ -1786,30 +1771,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(Scheduler).toHaveYielded(['Promise resolved [B2]']);
- if (
- ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
- ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
- ) {
- expect(Scheduler).toFlushAndYield([
- 'B2',
- 'Destroy Layout Effect [Loading...]',
- 'Destroy Layout Effect [B]',
- 'Layout Effect [B2]',
- 'Destroy Effect [Loading...]',
- 'Destroy Effect [B]',
- 'Effect [B2]',
- ]);
- } else {
- expect(Scheduler).toFlushAndYield([
- 'B2',
- 'Destroy Layout Effect [Loading...]',
- 'Destroy Effect [Loading...]',
- 'Destroy Layout Effect [B]',
- 'Layout Effect [B2]',
- 'Destroy Effect [B]',
- 'Effect [B2]',
- ]);
- }
+ expect(Scheduler).toFlushAndYield([
+ 'B2',
+ 'Destroy Layout Effect [Loading...]',
+ 'Destroy Layout Effect [B]',
+ 'Layout Effect [B2]',
+ 'Destroy Effect [Loading...]',
+ 'Destroy Effect [B]',
+ 'Effect [B2]',
+ ]);
});
it('suspends for longer if something took a long (CPU bound) time to render', async () => {
diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js
index 63f596f391..bccca51040 100644
--- a/packages/react/src/__tests__/ReactProfiler-test.internal.js
+++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js
@@ -24,7 +24,6 @@ let TextResource;
let resourcePromise;
function loadModules({
- deferPassiveEffectCleanupDuringUnmount = false,
enableProfilerTimer = true,
enableProfilerCommitHooks = true,
enableSchedulerTracing = true,
@@ -33,8 +32,6 @@ function loadModules({
} = {}) {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = deferPassiveEffectCleanupDuringUnmount;
- ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates = deferPassiveEffectCleanupDuringUnmount;
ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks;
ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
@@ -186,1872 +183,2173 @@ describe('Profiler', () => {
});
});
- [true, false].forEach(deferPassiveEffectCleanupDuringUnmount => {
- [true, false].forEach(enableSchedulerTracing => {
- describe(`onRender enableSchedulerTracing:${
- enableSchedulerTracing ? 'enabled' : 'disabled'
- } deferPassiveEffectCleanupDuringUnmount:${
- deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
- }`, () => {
- beforeEach(() => {
- jest.resetModules();
-
- loadModules({
- deferPassiveEffectCleanupDuringUnmount,
- enableSchedulerTracing,
- });
- });
-
- it('should handle errors thrown', () => {
- const callback = jest.fn(id => {
- if (id === 'throw') {
- throw Error('expected');
- }
- });
-
- let didMount = false;
- class ClassComponent extends React.Component {
- componentDidMount() {
- didMount = true;
- }
- render() {
- return this.props.children;
- }
- }
-
- // Errors thrown from onRender should not break the commit phase,
- // Or prevent other lifecycles from being called.
- expect(() =>
- ReactTestRenderer.create(
-
-
-
-
-
-
- ,
- ),
- ).toThrow('expected');
- expect(didMount).toBe(true);
- expect(callback).toHaveBeenCalledTimes(2);
- });
-
- it('is not invoked until the commit phase', () => {
- const callback = jest.fn();
-
- const Yield = ({value}) => {
- Scheduler.unstable_yieldValue(value);
- return null;
- };
-
- ReactTestRenderer.create(
-
-
-
- ,
- {
- unstable_isConcurrent: true,
- },
- );
-
- // Times are logged until a render is committed.
- expect(Scheduler).toFlushAndYieldThrough(['first']);
- expect(callback).toHaveBeenCalledTimes(0);
- expect(Scheduler).toFlushAndYield(['last']);
- expect(callback).toHaveBeenCalledTimes(1);
- });
-
- it('does not record times for components outside of Profiler tree', () => {
- // Mock the Scheduler module so we can track how many times the current
- // time is read
- jest.mock('scheduler', obj => {
- const ActualScheduler = require.requireActual(
- 'scheduler/unstable_mock',
- );
- return {
- ...ActualScheduler,
- unstable_now: function mockUnstableNow() {
- ActualScheduler.unstable_yieldValue('read current time');
- return ActualScheduler.unstable_now();
- },
- };
- });
-
- jest.resetModules();
-
- loadModules({enableSchedulerTracing});
-
- // Clear yields in case the current time is read during initialization.
- Scheduler.unstable_clearYields();
-
- ReactTestRenderer.create(
- ,
- );
-
- // Should be called two times:
- // 1. To compute the update expiration time
- // 2. To record the commit time
- // No additional calls from ProfilerTimer are expected.
- expect(Scheduler).toHaveYielded(
- gate(flags =>
- flags.new
- ? [
- // The new reconciler reads the current time in more places,
- // to detect starvation. This is unrelated to the profiler,
- // which happens to use the same Scheduler method that we
- // mocked above. We should rewrite this test so that it's
- // less fragile.
- 'read current time',
- 'read current time',
- 'read current time',
- 'read current time',
- ]
- : ['read current time', 'read current time'],
- ),
- );
-
- // Restore original mock
- jest.mock('scheduler', () =>
- require.requireActual('scheduler/unstable_mock'),
- );
- });
-
- it('does not report work done on a sibling', () => {
- const callback = jest.fn();
-
- const DoesNotUpdate = React.memo(
- function DoesNotUpdateInner() {
- Scheduler.unstable_advanceTime(10);
- return null;
- },
- () => true,
- );
-
- let updateProfilerSibling;
-
- function ProfilerSibling() {
- const [count, setCount] = React.useState(0);
- updateProfilerSibling = () => setCount(count + 1);
- return null;
- }
-
- function App() {
- return (
-
-
-
-
-
-
- );
- }
-
- const renderer = ReactTestRenderer.create();
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- let call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
- expect(call[0]).toBe('test');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(10); // actual time
- expect(call[3]).toBe(10); // base time
- expect(call[4]).toBe(0); // start time
- expect(call[5]).toBe(10); // commit time
- expect(call[6]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- callback.mockReset();
-
- Scheduler.unstable_advanceTime(20); // 10 -> 30
-
- // Updating a parent should report a re-render,
- // since React technically did a little bit of work between the Profiler and the bailed out subtree.
- renderer.update();
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
- expect(call[0]).toBe('test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(0); // actual time
- expect(call[3]).toBe(10); // base time
- expect(call[4]).toBe(30); // start time
- expect(call[5]).toBe(30); // commit time
- expect(call[6]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- callback.mockReset();
-
- Scheduler.unstable_advanceTime(20); // 30 -> 50
-
- // Updating a sibling should not report a re-render.
- ReactTestRenderer.act(updateProfilerSibling);
-
- expect(callback).not.toHaveBeenCalled();
- });
-
- it('logs render times for both mount and update', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- let [call] = callback.mock.calls;
-
- expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
- expect(call[0]).toBe('test');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(10); // actual time
- expect(call[3]).toBe(10); // base time
- expect(call[4]).toBe(5); // start time
- expect(call[5]).toBe(15); // commit time
- expect(call[6]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- callback.mockReset();
-
- Scheduler.unstable_advanceTime(20); // 15 -> 35
-
- renderer.update(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- [call] = callback.mock.calls;
-
- expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
- expect(call[0]).toBe('test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(10); // actual time
- expect(call[3]).toBe(10); // base time
- expect(call[4]).toBe(35); // start time
- expect(call[5]).toBe(45); // commit time
- expect(call[6]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- callback.mockReset();
-
- Scheduler.unstable_advanceTime(20); // 45 -> 65
-
- renderer.update(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- [call] = callback.mock.calls;
-
- expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
- expect(call[0]).toBe('test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(4); // actual time
- expect(call[3]).toBe(4); // base time
- expect(call[4]).toBe(65); // start time
- expect(call[5]).toBe(69); // commit time
- expect(call[6]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- it('includes render times of nested Profilers in their parent times', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- ReactTestRenderer.create(
-
-
-
-
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- // Callbacks bubble (reverse order).
- const [childCall, parentCall] = callback.mock.calls;
- expect(childCall[0]).toBe('child');
- expect(parentCall[0]).toBe('parent');
-
- // Parent times should include child times
- expect(childCall[2]).toBe(20); // actual time
- expect(childCall[3]).toBe(20); // base time
- expect(childCall[4]).toBe(15); // start time
- expect(childCall[5]).toBe(35); // commit time
- expect(parentCall[2]).toBe(30); // actual time
- expect(parentCall[3]).toBe(30); // base time
- expect(parentCall[4]).toBe(5); // start time
- expect(parentCall[5]).toBe(35); // commit time
- });
-
- it('traces sibling Profilers separately', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- ReactTestRenderer.create(
-
-
-
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- const [firstCall, secondCall] = callback.mock.calls;
- expect(firstCall[0]).toBe('first');
- expect(secondCall[0]).toBe('second');
-
- // Parent times should include child times
- expect(firstCall[2]).toBe(20); // actual time
- expect(firstCall[3]).toBe(20); // base time
- expect(firstCall[4]).toBe(5); // start time
- expect(firstCall[5]).toBe(30); // commit time
- expect(secondCall[2]).toBe(5); // actual time
- expect(secondCall[3]).toBe(5); // base time
- expect(secondCall[4]).toBe(25); // start time
- expect(secondCall[5]).toBe(30); // commit time
- });
-
- it('does not include time spent outside of profile root', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- ReactTestRenderer.create(
-
-
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- const [call] = callback.mock.calls;
- expect(call[0]).toBe('test');
- expect(call[2]).toBe(5); // actual time
- expect(call[3]).toBe(5); // base time
- expect(call[4]).toBe(25); // start time
- expect(call[5]).toBe(50); // commit time
- });
-
- it('is not called when blocked by sCU false', () => {
- const callback = jest.fn();
-
- let instance;
- class Updater extends React.Component {
- state = {};
- render() {
- instance = this;
- return this.props.children;
- }
- }
-
- const renderer = ReactTestRenderer.create(
-
-
-
-
-
-
- ,
- );
-
- // All profile callbacks are called for initial render
- expect(callback).toHaveBeenCalledTimes(2);
-
- callback.mockReset();
-
- renderer.unstable_flushSync(() => {
- instance.setState({
- count: 1,
- });
- });
-
- // Only call onRender for paths that have re-rendered.
- // Since the Updater's props didn't change,
- // React does not re-render its children.
- expect(callback).toHaveBeenCalledTimes(1);
- expect(callback.mock.calls[0][0]).toBe('outer');
- });
-
- it('decreases actual time but not base time when sCU prevents an update', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- Scheduler.unstable_advanceTime(30); // 28 -> 58
-
- renderer.update(
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- const [mountCall, updateCall] = callback.mock.calls;
-
- expect(mountCall[1]).toBe('mount');
- expect(mountCall[2]).toBe(23); // actual time
- expect(mountCall[3]).toBe(23); // base time
- expect(mountCall[4]).toBe(5); // start time
- expect(mountCall[5]).toBe(28); // commit time
-
- expect(updateCall[1]).toBe('update');
- expect(updateCall[2]).toBe(4); // actual time
- expect(updateCall[3]).toBe(17); // base time
- expect(updateCall[4]).toBe(58); // start time
- expect(updateCall[5]).toBe(62); // commit time
- });
-
- it('includes time spent in render phase lifecycles', () => {
- class WithLifecycles extends React.Component {
- state = {};
- static getDerivedStateFromProps() {
- Scheduler.unstable_advanceTime(3);
- return null;
- }
- shouldComponentUpdate() {
- Scheduler.unstable_advanceTime(7);
- return true;
- }
- render() {
- Scheduler.unstable_advanceTime(5);
- return null;
- }
- }
-
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
- ,
- );
-
- Scheduler.unstable_advanceTime(15); // 13 -> 28
-
- renderer.update(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- const [mountCall, updateCall] = callback.mock.calls;
-
- expect(mountCall[1]).toBe('mount');
- expect(mountCall[2]).toBe(8); // actual time
- expect(mountCall[3]).toBe(8); // base time
- expect(mountCall[4]).toBe(5); // start time
- expect(mountCall[5]).toBe(13); // commit time
-
- expect(updateCall[1]).toBe('update');
- expect(updateCall[2]).toBe(15); // actual time
- expect(updateCall[3]).toBe(15); // base time
- expect(updateCall[4]).toBe(28); // start time
- expect(updateCall[5]).toBe(43); // commit time
- });
-
- describe('with regard to interruptions', () => {
- it('should accumulate actual time after a scheduling interruptions', () => {
- const callback = jest.fn();
-
- const Yield = ({renderTime}) => {
- Scheduler.unstable_advanceTime(renderTime);
- Scheduler.unstable_yieldValue('Yield:' + renderTime);
- return null;
- };
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- // Render partially, but run out of time before completing.
- ReactTestRenderer.create(
-
-
-
- ,
- {unstable_isConcurrent: true},
- );
- expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Resume render for remaining children.
- expect(Scheduler).toFlushAndYield(['Yield:3']);
-
- // Verify that logged times include both durations above.
- expect(callback).toHaveBeenCalledTimes(1);
- const [call] = callback.mock.calls;
- expect(call[2]).toBe(5); // actual time
- expect(call[3]).toBe(5); // base time
- expect(call[4]).toBe(5); // start time
- expect(call[5]).toBe(10); // commit time
- });
-
- it('should not include time between frames', () => {
- const callback = jest.fn();
-
- const Yield = ({renderTime}) => {
- Scheduler.unstable_advanceTime(renderTime);
- Scheduler.unstable_yieldValue('Yield:' + renderTime);
- return null;
- };
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- // Render partially, but don't finish.
- // This partial render should take 5ms of simulated time.
- ReactTestRenderer.create(
-
-
-
-
-
-
- ,
- {unstable_isConcurrent: true},
- );
- expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(50); // 10 -> 60
-
- // Flush the remaining work,
- // Which should take an additional 10ms of simulated time.
- expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
- expect(callback).toHaveBeenCalledTimes(2);
-
- const [innerCall, outerCall] = callback.mock.calls;
-
- // Verify that the actual time includes all work times,
- // But not the time that elapsed between frames.
- expect(innerCall[0]).toBe('inner');
- expect(innerCall[2]).toBe(17); // actual time
- expect(innerCall[3]).toBe(17); // base time
- expect(innerCall[4]).toBe(70); // start time
- expect(innerCall[5]).toBe(87); // commit time
- expect(outerCall[0]).toBe('outer');
- expect(outerCall[2]).toBe(32); // actual time
- expect(outerCall[3]).toBe(32); // base time
- expect(outerCall[4]).toBe(5); // start time
- expect(outerCall[5]).toBe(87); // commit time
- });
-
- it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
- const callback = jest.fn();
-
- const Yield = ({renderTime}) => {
- Scheduler.unstable_advanceTime(renderTime);
- Scheduler.unstable_yieldValue('Yield:' + renderTime);
- return null;
- };
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- // Render a partially update, but don't finish.
- // This partial render should take 10ms of simulated time.
- const renderer = ReactTestRenderer.create(
-
-
-
- ,
- {unstable_isConcurrent: true},
- );
- expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(100); // 15 -> 115
-
- // Interrupt with higher priority work.
- // The interrupted work simulates an additional 5ms of time.
- renderer.unstable_flushSync(() => {
- renderer.update(
-
-
- ,
- );
- });
- expect(Scheduler).toHaveYielded(['Yield:5']);
-
- // The initial work was thrown away in this case,
- // So the actual and base times should only include the final rendered tree times.
- expect(callback).toHaveBeenCalledTimes(1);
- const call = callback.mock.calls[0];
- expect(call[2]).toBe(5); // actual time
- expect(call[3]).toBe(5); // base time
- expect(call[4]).toBe(115); // start time
- expect(call[5]).toBe(120); // commit time
-
- callback.mockReset();
-
- // Verify no more unexpected callbacks from low priority work
- expect(Scheduler).toFlushWithoutYielding();
- expect(callback).toHaveBeenCalledTimes(0);
- });
-
- it('should report the expected times when a high-priority update replaces a low-priority update', () => {
- const callback = jest.fn();
-
- const Yield = ({renderTime}) => {
- Scheduler.unstable_advanceTime(renderTime);
- Scheduler.unstable_yieldValue('Yield:' + renderTime);
- return null;
- };
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
-
- ,
- {unstable_isConcurrent: true},
- );
-
- // Render everything initially.
- // This should take 21 seconds of actual and base time.
- expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
- expect(callback).toHaveBeenCalledTimes(1);
- let call = callback.mock.calls[0];
- expect(call[2]).toBe(21); // actual time
- expect(call[3]).toBe(21); // base time
- expect(call[4]).toBe(5); // start time
- expect(call[5]).toBe(26); // commit time
-
- callback.mockReset();
-
- Scheduler.unstable_advanceTime(30); // 26 -> 56
-
- // Render a partially update, but don't finish.
- // This partial render should take 3ms of simulated time.
- renderer.update(
-
-
-
-
- ,
- );
- expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(100); // 59 -> 159
-
- // Render another 5ms of simulated time.
- expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(100); // 164 -> 264
-
- // Interrupt with higher priority work.
- // The interrupted work simulates an additional 11ms of time.
- renderer.unstable_flushSync(() => {
- renderer.update(
-
-
- ,
- );
- });
- expect(Scheduler).toHaveYielded(['Yield:11']);
-
- // The actual time should include only the most recent render,
- // Because this lets us avoid a lot of commit phase reset complexity.
- // The base time includes only the final rendered tree times.
- expect(callback).toHaveBeenCalledTimes(1);
- call = callback.mock.calls[0];
- expect(call[2]).toBe(11); // actual time
- expect(call[3]).toBe(11); // base time
- expect(call[4]).toBe(264); // start time
- expect(call[5]).toBe(275); // commit time
-
- // Verify no more unexpected callbacks from low priority work
- expect(Scheduler).toFlushAndYield([]);
- expect(callback).toHaveBeenCalledTimes(1);
- });
-
- it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
- const callback = jest.fn();
-
- const Yield = ({renderTime}) => {
- Scheduler.unstable_advanceTime(renderTime);
- Scheduler.unstable_yieldValue('Yield:' + renderTime);
- return null;
- };
-
- let first;
- class FirstComponent extends React.Component {
- state = {renderTime: 1};
- render() {
- first = this;
- Scheduler.unstable_advanceTime(this.state.renderTime);
- Scheduler.unstable_yieldValue(
- 'FirstComponent:' + this.state.renderTime,
- );
- return ;
- }
- }
- let second;
- class SecondComponent extends React.Component {
- state = {renderTime: 2};
- render() {
- second = this;
- Scheduler.unstable_advanceTime(this.state.renderTime);
- Scheduler.unstable_yieldValue(
- 'SecondComponent:' + this.state.renderTime,
- );
- return ;
- }
- }
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
-
- ,
- {unstable_isConcurrent: true},
- );
-
- // Render everything initially.
- // This simulates a total of 14ms of actual render time.
- // The base render time is also 14ms for the initial render.
- expect(Scheduler).toFlushAndYield([
- 'FirstComponent:1',
- 'Yield:4',
- 'SecondComponent:2',
- 'Yield:7',
- ]);
- expect(callback).toHaveBeenCalledTimes(1);
- let call = callback.mock.calls[0];
- expect(call[2]).toBe(14); // actual time
- expect(call[3]).toBe(14); // base time
- expect(call[4]).toBe(5); // start time
- expect(call[5]).toBe(19); // commit time
-
- callback.mockClear();
-
- Scheduler.unstable_advanceTime(100); // 19 -> 119
-
- // Render a partially update, but don't finish.
- // This partial render will take 10ms of actual render time.
- first.setState({renderTime: 10});
- expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
- expect(callback).toHaveBeenCalledTimes(0);
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(100); // 129 -> 229
-
- // Interrupt with higher priority work.
- // This simulates a total of 37ms of actual render time.
- renderer.unstable_flushSync(() =>
- second.setState({renderTime: 30}),
- );
- expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
-
- // The actual time should include only the most recent render (37ms),
- // Because this greatly simplifies the commit phase logic.
- // The base time should include the more recent times for the SecondComponent subtree,
- // As well as the original times for the FirstComponent subtree.
- expect(callback).toHaveBeenCalledTimes(1);
- call = callback.mock.calls[0];
- expect(call[2]).toBe(37); // actual time
- expect(call[3]).toBe(42); // base time
- expect(call[4]).toBe(229); // start time
- expect(call[5]).toBe(266); // commit time
-
- callback.mockClear();
-
- // Simulate time moving forward while frame is paused.
- Scheduler.unstable_advanceTime(100); // 266 -> 366
-
- // Resume the original low priority update, with rebased state.
- // This simulates a total of 14ms of actual render time,
- // And does not include the original (interrupted) 10ms.
- // The tree contains 42ms of base render time at this point,
- // Reflecting the most recent (longer) render durations.
- // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
- expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
- expect(callback).toHaveBeenCalledTimes(1);
- call = callback.mock.calls[0];
- expect(call[2]).toBe(14); // actual time
- expect(call[3]).toBe(51); // base time
- expect(call[4]).toBe(366); // start time
- expect(call[5]).toBe(380); // commit time
- });
-
- [true, false].forEach(
- replayFailedUnitOfWorkWithInvokeGuardedCallback => {
- describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
- replayFailedUnitOfWorkWithInvokeGuardedCallback
- ? 'enabled'
- : 'disabled'
- }`, () => {
- beforeEach(() => {
- jest.resetModules();
-
- loadModules({
- replayFailedUnitOfWorkWithInvokeGuardedCallback,
- });
- });
-
- it('should accumulate actual time after an error handled by componentDidCatch()', () => {
- const callback = jest.fn();
-
- const ThrowsError = ({unused}) => {
- Scheduler.unstable_advanceTime(3);
- throw Error('expected error');
- };
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- componentDidCatch(error) {
- this.setState({error});
- }
- render() {
- Scheduler.unstable_advanceTime(2);
- return this.state.error === null ? (
- this.props.children
- ) : (
-
- );
- }
- }
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- ReactTestRenderer.create(
-
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- // Callbacks bubble (reverse order).
- const [mountCall, updateCall] = callback.mock.calls;
-
- // The initial mount only includes the ErrorBoundary (which takes 2)
- // But it spends time rendering all of the failed subtree also.
- expect(mountCall[1]).toBe('mount');
- // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
- // We don't count the time spent in replaying the failed unit of work (ThrowsError)
- expect(mountCall[2]).toBe(14);
- // base time includes: 2 (ErrorBoundary)
- // Since the tree is empty for the initial commit
- expect(mountCall[3]).toBe(2);
- // start time
- expect(mountCall[4]).toBe(5);
- // commit time: 5 initially + 14 of work
- // Add an additional 3 (ThrowsError) if we replayed the failed work
- expect(mountCall[5]).toBe(
- __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
- ? 22
- : 19,
- );
-
- // The update includes the ErrorBoundary and its fallback child
- expect(updateCall[1]).toBe('update');
- // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
- expect(updateCall[2]).toBe(22);
- // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
- expect(updateCall[3]).toBe(22);
- // start time
- expect(updateCall[4]).toBe(
- __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
- ? 22
- : 19,
- );
- // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
- // Add an additional 3 (ThrowsError) if we replayed the failed work
- expect(updateCall[5]).toBe(
- __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
- ? 44
- : 41,
- );
- });
-
- it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
- const callback = jest.fn();
-
- const ThrowsError = ({unused}) => {
- Scheduler.unstable_advanceTime(10);
- throw Error('expected error');
- };
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- static getDerivedStateFromError(error) {
- return {error};
- }
- render() {
- Scheduler.unstable_advanceTime(2);
- return this.state.error === null ? (
- this.props.children
- ) : (
-
- );
- }
- }
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- ReactTestRenderer.create(
-
-
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- // Callbacks bubble (reverse order).
- const [mountCall] = callback.mock.calls;
-
- // The initial mount includes the ErrorBoundary's error state,
- // But it also spends actual time rendering UI that fails and isn't included.
- expect(mountCall[1]).toBe('mount');
- // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
- // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
- // We don't count the time spent in replaying the failed unit of work (ThrowsError)
- expect(mountCall[2]).toBe(39);
- // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
- expect(mountCall[3]).toBe(22);
- // start time
- expect(mountCall[4]).toBe(5);
- // commit time
- expect(mountCall[5]).toBe(
- __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
- ? 54
- : 44,
- );
- });
-
- it('should reset the fiber stack correct after a "complete" phase error', () => {
- jest.resetModules();
-
- loadModules({
- useNoopRenderer: true,
- replayFailedUnitOfWorkWithInvokeGuardedCallback,
- });
-
- // Simulate a renderer error during the "complete" phase.
- // This mimics behavior like React Native's View/Text nesting validation.
- ReactNoop.render(
-
- hi
- ,
- );
- expect(Scheduler).toFlushAndThrow('Error in host config.');
-
- // A similar case we've seen caused by an invariant in ReactDOM.
- // It didn't reproduce without a host component inside.
- ReactNoop.render(
-
-
- hi
-
- ,
- );
- expect(Scheduler).toFlushAndThrow('Error in host config.');
-
- // So long as the profiler timer's fiber stack is reset correctly,
- // Subsequent renders should not error.
- ReactNoop.render(
-
- hi
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- });
- });
- },
- );
- });
-
- it('reflects the most recently rendered id value', () => {
- const callback = jest.fn();
-
- Scheduler.unstable_advanceTime(5); // 0 -> 5
-
- const renderer = ReactTestRenderer.create(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- Scheduler.unstable_advanceTime(20); // 7 -> 27
-
- renderer.update(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- const [mountCall, updateCall] = callback.mock.calls;
-
- expect(mountCall[0]).toBe('one');
- expect(mountCall[1]).toBe('mount');
- expect(mountCall[2]).toBe(2); // actual time
- expect(mountCall[3]).toBe(2); // base time
- expect(mountCall[4]).toBe(5); // start time
-
- expect(updateCall[0]).toBe('two');
- expect(updateCall[1]).toBe('update');
- expect(updateCall[2]).toBe(1); // actual time
- expect(updateCall[3]).toBe(1); // base time
- expect(updateCall[4]).toBe(27); // start time
- });
-
- it('should not be called until after mutations', () => {
- let classComponentMounted = false;
- const callback = jest.fn(
- (
- id,
- phase,
- actualDuration,
- baseDuration,
- startTime,
- commitTime,
- ) => {
- // Don't call this hook until after mutations
- expect(classComponentMounted).toBe(true);
- // But the commit time should reflect pre-mutation
- expect(commitTime).toBe(2);
- },
- );
-
- class ClassComponent extends React.Component {
- componentDidMount() {
- Scheduler.unstable_advanceTime(5);
- classComponentMounted = true;
- }
- render() {
- Scheduler.unstable_advanceTime(2);
- return null;
- }
- }
-
- ReactTestRenderer.create(
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(1);
+ [true, false].forEach(enableSchedulerTracing => {
+ describe(`onRender enableSchedulerTracing:${
+ enableSchedulerTracing ? 'enabled' : 'disabled'
+ }`, () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ loadModules({
+ enableSchedulerTracing,
});
});
- describe(`onCommit enableSchedulerTracing:${
- enableSchedulerTracing ? 'enabled' : 'disabled'
- } deferPassiveEffectCleanupDuringUnmount:${
- deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
- }`, () => {
- beforeEach(() => {
- jest.resetModules();
+ it('should handle errors thrown', () => {
+ const callback = jest.fn(id => {
+ if (id === 'throw') {
+ throw Error('expected');
+ }
+ });
- loadModules({
- deferPassiveEffectCleanupDuringUnmount,
- enableSchedulerTracing,
+ let didMount = false;
+ class ClassComponent extends React.Component {
+ componentDidMount() {
+ didMount = true;
+ }
+ render() {
+ return this.props.children;
+ }
+ }
+
+ // Errors thrown from onRender should not break the commit phase,
+ // Or prevent other lifecycles from being called.
+ expect(() =>
+ ReactTestRenderer.create(
+
+
+
+
+
+
+ ,
+ ),
+ ).toThrow('expected');
+ expect(didMount).toBe(true);
+ expect(callback).toHaveBeenCalledTimes(2);
+ });
+
+ it('is not invoked until the commit phase', () => {
+ const callback = jest.fn();
+
+ const Yield = ({value}) => {
+ Scheduler.unstable_yieldValue(value);
+ return null;
+ };
+
+ ReactTestRenderer.create(
+
+
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ // Times are logged until a render is committed.
+ expect(Scheduler).toFlushAndYieldThrough(['first']);
+ expect(callback).toHaveBeenCalledTimes(0);
+ expect(Scheduler).toFlushAndYield(['last']);
+ expect(callback).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not record times for components outside of Profiler tree', () => {
+ // Mock the Scheduler module so we can track how many times the current
+ // time is read
+ jest.mock('scheduler', obj => {
+ const ActualScheduler = require.requireActual(
+ 'scheduler/unstable_mock',
+ );
+ return {
+ ...ActualScheduler,
+ unstable_now: function mockUnstableNow() {
+ ActualScheduler.unstable_yieldValue('read current time');
+ return ActualScheduler.unstable_now();
+ },
+ };
+ });
+
+ jest.resetModules();
+
+ loadModules({enableSchedulerTracing});
+
+ // Clear yields in case the current time is read during initialization.
+ Scheduler.unstable_clearYields();
+
+ ReactTestRenderer.create(
+ ,
+ );
+
+ // Should be called two times:
+ // 1. To compute the update expiration time
+ // 2. To record the commit time
+ // No additional calls from ProfilerTimer are expected.
+ expect(Scheduler).toHaveYielded(
+ gate(flags =>
+ flags.new
+ ? [
+ // The new reconciler reads the current time in more places,
+ // to detect starvation. This is unrelated to the profiler,
+ // which happens to use the same Scheduler method that we
+ // mocked above. We should rewrite this test so that it's
+ // less fragile.
+ 'read current time',
+ 'read current time',
+ 'read current time',
+ 'read current time',
+ ]
+ : ['read current time', 'read current time'],
+ ),
+ );
+
+ // Restore original mock
+ jest.mock('scheduler', () =>
+ require.requireActual('scheduler/unstable_mock'),
+ );
+ });
+
+ it('does not report work done on a sibling', () => {
+ const callback = jest.fn();
+
+ const DoesNotUpdate = React.memo(
+ function DoesNotUpdateInner() {
+ Scheduler.unstable_advanceTime(10);
+ return null;
+ },
+ () => true,
+ );
+
+ let updateProfilerSibling;
+
+ function ProfilerSibling() {
+ const [count, setCount] = React.useState(0);
+ updateProfilerSibling = () => setCount(count + 1);
+ return null;
+ }
+
+ function App() {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ const renderer = ReactTestRenderer.create();
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
+ expect(call[0]).toBe('test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(10); // actual time
+ expect(call[3]).toBe(10); // base time
+ expect(call[4]).toBe(0); // start time
+ expect(call[5]).toBe(10); // commit time
+ expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockReset();
+
+ Scheduler.unstable_advanceTime(20); // 10 -> 30
+
+ // Updating a parent should report a re-render,
+ // since React technically did a little bit of work between the Profiler and the bailed out subtree.
+ renderer.update();
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
+ expect(call[0]).toBe('test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(0); // actual time
+ expect(call[3]).toBe(10); // base time
+ expect(call[4]).toBe(30); // start time
+ expect(call[5]).toBe(30); // commit time
+ expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockReset();
+
+ Scheduler.unstable_advanceTime(20); // 30 -> 50
+
+ // Updating a sibling should not report a re-render.
+ ReactTestRenderer.act(updateProfilerSibling);
+
+ expect(callback).not.toHaveBeenCalled();
+ });
+
+ it('logs render times for both mount and update', () => {
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let [call] = callback.mock.calls;
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
+ expect(call[0]).toBe('test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(10); // actual time
+ expect(call[3]).toBe(10); // base time
+ expect(call[4]).toBe(5); // start time
+ expect(call[5]).toBe(15); // commit time
+ expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockReset();
+
+ Scheduler.unstable_advanceTime(20); // 15 -> 35
+
+ renderer.update(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ [call] = callback.mock.calls;
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
+ expect(call[0]).toBe('test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(10); // actual time
+ expect(call[3]).toBe(10); // base time
+ expect(call[4]).toBe(35); // start time
+ expect(call[5]).toBe(45); // commit time
+ expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockReset();
+
+ Scheduler.unstable_advanceTime(20); // 45 -> 65
+
+ renderer.update(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ [call] = callback.mock.calls;
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
+ expect(call[0]).toBe('test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(4); // actual time
+ expect(call[3]).toBe(4); // base time
+ expect(call[4]).toBe(65); // start time
+ expect(call[5]).toBe(69); // commit time
+ expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('includes render times of nested Profilers in their parent times', () => {
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ ReactTestRenderer.create(
+
+
+
+
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ // Callbacks bubble (reverse order).
+ const [childCall, parentCall] = callback.mock.calls;
+ expect(childCall[0]).toBe('child');
+ expect(parentCall[0]).toBe('parent');
+
+ // Parent times should include child times
+ expect(childCall[2]).toBe(20); // actual time
+ expect(childCall[3]).toBe(20); // base time
+ expect(childCall[4]).toBe(15); // start time
+ expect(childCall[5]).toBe(35); // commit time
+ expect(parentCall[2]).toBe(30); // actual time
+ expect(parentCall[3]).toBe(30); // base time
+ expect(parentCall[4]).toBe(5); // start time
+ expect(parentCall[5]).toBe(35); // commit time
+ });
+
+ it('traces sibling Profilers separately', () => {
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ ReactTestRenderer.create(
+
+
+
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ const [firstCall, secondCall] = callback.mock.calls;
+ expect(firstCall[0]).toBe('first');
+ expect(secondCall[0]).toBe('second');
+
+ // Parent times should include child times
+ expect(firstCall[2]).toBe(20); // actual time
+ expect(firstCall[3]).toBe(20); // base time
+ expect(firstCall[4]).toBe(5); // start time
+ expect(firstCall[5]).toBe(30); // commit time
+ expect(secondCall[2]).toBe(5); // actual time
+ expect(secondCall[3]).toBe(5); // base time
+ expect(secondCall[4]).toBe(25); // start time
+ expect(secondCall[5]).toBe(30); // commit time
+ });
+
+ it('does not include time spent outside of profile root', () => {
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ ReactTestRenderer.create(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ const [call] = callback.mock.calls;
+ expect(call[0]).toBe('test');
+ expect(call[2]).toBe(5); // actual time
+ expect(call[3]).toBe(5); // base time
+ expect(call[4]).toBe(25); // start time
+ expect(call[5]).toBe(50); // commit time
+ });
+
+ it('is not called when blocked by sCU false', () => {
+ const callback = jest.fn();
+
+ let instance;
+ class Updater extends React.Component {
+ state = {};
+ render() {
+ instance = this;
+ return this.props.children;
+ }
+ }
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+
+
+
+ ,
+ );
+
+ // All profile callbacks are called for initial render
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ callback.mockReset();
+
+ renderer.unstable_flushSync(() => {
+ instance.setState({
+ count: 1,
});
});
- it('should report time spent in layout effects and commit lifecycles', () => {
+ // Only call onRender for paths that have re-rendered.
+ // Since the Updater's props didn't change,
+ // React does not re-render its children.
+ expect(callback).toHaveBeenCalledTimes(1);
+ expect(callback.mock.calls[0][0]).toBe('outer');
+ });
+
+ it('decreases actual time but not base time when sCU prevents an update', () => {
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ Scheduler.unstable_advanceTime(30); // 28 -> 58
+
+ renderer.update(
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ const [mountCall, updateCall] = callback.mock.calls;
+
+ expect(mountCall[1]).toBe('mount');
+ expect(mountCall[2]).toBe(23); // actual time
+ expect(mountCall[3]).toBe(23); // base time
+ expect(mountCall[4]).toBe(5); // start time
+ expect(mountCall[5]).toBe(28); // commit time
+
+ expect(updateCall[1]).toBe('update');
+ expect(updateCall[2]).toBe(4); // actual time
+ expect(updateCall[3]).toBe(17); // base time
+ expect(updateCall[4]).toBe(58); // start time
+ expect(updateCall[5]).toBe(62); // commit time
+ });
+
+ it('includes time spent in render phase lifecycles', () => {
+ class WithLifecycles extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ Scheduler.unstable_advanceTime(3);
+ return null;
+ }
+ shouldComponentUpdate() {
+ Scheduler.unstable_advanceTime(7);
+ return true;
+ }
+ render() {
+ Scheduler.unstable_advanceTime(5);
+ return null;
+ }
+ }
+
+ const callback = jest.fn();
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+ ,
+ );
+
+ Scheduler.unstable_advanceTime(15); // 13 -> 28
+
+ renderer.update(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ const [mountCall, updateCall] = callback.mock.calls;
+
+ expect(mountCall[1]).toBe('mount');
+ expect(mountCall[2]).toBe(8); // actual time
+ expect(mountCall[3]).toBe(8); // base time
+ expect(mountCall[4]).toBe(5); // start time
+ expect(mountCall[5]).toBe(13); // commit time
+
+ expect(updateCall[1]).toBe('update');
+ expect(updateCall[2]).toBe(15); // actual time
+ expect(updateCall[3]).toBe(15); // base time
+ expect(updateCall[4]).toBe(28); // start time
+ expect(updateCall[5]).toBe(43); // commit time
+ });
+
+ describe('with regard to interruptions', () => {
+ it('should accumulate actual time after a scheduling interruptions', () => {
const callback = jest.fn();
- const ComponetWithEffects = () => {
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(10);
- return () => {
- Scheduler.unstable_advanceTime(100);
- };
- }, []);
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(1000);
- return () => {
- Scheduler.unstable_advanceTime(10000);
- };
- });
- React.useEffect(() => {
- // This passive effect is here to verify that its time isn't reported.
- Scheduler.unstable_advanceTime(5);
- return () => {
- Scheduler.unstable_advanceTime(7);
- };
- });
+ const Yield = ({renderTime}) => {
+ Scheduler.unstable_advanceTime(renderTime);
+ Scheduler.unstable_yieldValue('Yield:' + renderTime);
return null;
};
- class ComponentWithCommitHooks extends React.Component {
- componentDidMount() {
- Scheduler.unstable_advanceTime(100000);
- }
- componentDidUpdate() {
- Scheduler.unstable_advanceTime(1000000);
- }
- render() {
- return null;
- }
- }
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
- Scheduler.unstable_advanceTime(1);
-
- const renderer = ReactTestRenderer.create(
-
-
-
+ // Render partially, but run out of time before completing.
+ ReactTestRenderer.create(
+
+
+
,
+ {unstable_isConcurrent: true},
);
+ expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
+ expect(callback).toHaveBeenCalledTimes(0);
+ // Resume render for remaining children.
+ expect(Scheduler).toFlushAndYield(['Yield:3']);
+
+ // Verify that logged times include both durations above.
expect(callback).toHaveBeenCalledTimes(1);
-
- let call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(101010); // durations
- expect(call[3]).toBe(1); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- Scheduler.unstable_advanceTime(1);
-
- renderer.update(
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- call = callback.mock.calls[1];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('update-test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(1011000); // durations
- expect(call[3]).toBe(101017); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- Scheduler.unstable_advanceTime(1);
-
- renderer.update(
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(3);
-
- call = callback.mock.calls[2];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('unmount-test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(10100); // durations
- expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ const [call] = callback.mock.calls;
+ expect(call[2]).toBe(5); // actual time
+ expect(call[3]).toBe(5); // base time
+ expect(call[4]).toBe(5); // start time
+ expect(call[5]).toBe(10); // commit time
});
- it('should report time spent in layout effects and commit lifecycles with cascading renders', () => {
+ it('should not include time between frames', () => {
const callback = jest.fn();
- const ComponetWithEffects = ({shouldCascade}) => {
- const [didCascade, setDidCascade] = React.useState(false);
- React.useLayoutEffect(() => {
- if (shouldCascade && !didCascade) {
- setDidCascade(true);
- }
- Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
- return () => {
- Scheduler.unstable_advanceTime(100);
- };
- }, [didCascade, shouldCascade]);
+ const Yield = ({renderTime}) => {
+ Scheduler.unstable_advanceTime(renderTime);
+ Scheduler.unstable_yieldValue('Yield:' + renderTime);
return null;
};
- class ComponentWithCommitHooks extends React.Component {
- state = {
- didCascade: false,
- };
- componentDidMount() {
- Scheduler.unstable_advanceTime(1000);
- }
- componentDidUpdate() {
- Scheduler.unstable_advanceTime(10000);
- if (this.props.shouldCascade && !this.state.didCascade) {
- this.setState({didCascade: true});
- }
- }
- render() {
- return null;
- }
- }
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
- Scheduler.unstable_advanceTime(1);
-
- const renderer = ReactTestRenderer.create(
-
-
-
+ // Render partially, but don't finish.
+ // This partial render should take 5ms of simulated time.
+ ReactTestRenderer.create(
+
+
+
+
+
+
,
+ {unstable_isConcurrent: true},
);
+ expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
+ expect(callback).toHaveBeenCalledTimes(0);
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(50); // 10 -> 60
+
+ // Flush the remaining work,
+ // Which should take an additional 10ms of simulated time.
+ expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
expect(callback).toHaveBeenCalledTimes(2);
- let call = callback.mock.calls[0];
+ const [innerCall, outerCall] = callback.mock.calls;
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(1010); // durations
- expect(call[3]).toBe(1); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- call = callback.mock.calls[1];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(130); // durations
- expect(call[3]).toBe(1011); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- Scheduler.unstable_advanceTime(1);
-
- renderer.update(
-
-
-
- ,
- );
-
- expect(callback).toHaveBeenCalledTimes(4);
-
- call = callback.mock.calls[2];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('update-test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(10130); // durations
- expect(call[3]).toBe(1142); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- call = callback.mock.calls[3];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('update-test');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(10000); // durations
- expect(call[3]).toBe(11272); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ // Verify that the actual time includes all work times,
+ // But not the time that elapsed between frames.
+ expect(innerCall[0]).toBe('inner');
+ expect(innerCall[2]).toBe(17); // actual time
+ expect(innerCall[3]).toBe(17); // base time
+ expect(innerCall[4]).toBe(70); // start time
+ expect(innerCall[5]).toBe(87); // commit time
+ expect(outerCall[0]).toBe('outer');
+ expect(outerCall[2]).toBe(32); // actual time
+ expect(outerCall[3]).toBe(32); // base time
+ expect(outerCall[4]).toBe(5); // start time
+ expect(outerCall[5]).toBe(87); // commit time
});
- it('should bubble time spent in layout effects to higher profilers', () => {
+ it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
const callback = jest.fn();
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- setCountRef,
- }) => {
- const setCount = React.useState(0)[1];
- if (setCountRef != null) {
- setCountRef.current = setCount;
- }
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(duration);
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- };
- });
- Scheduler.unstable_advanceTime(1);
+ const Yield = ({renderTime}) => {
+ Scheduler.unstable_advanceTime(renderTime);
+ Scheduler.unstable_yieldValue('Yield:' + renderTime);
return null;
};
- const setCountRef = React.createRef(null);
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
- let renderer = null;
- ReactTestRenderer.act(() => {
- renderer = ReactTestRenderer.create(
-
-
-
-
-
-
-
- ,
- );
- });
+ // Render a partially update, but don't finish.
+ // This partial render should take 10ms of simulated time.
+ const renderer = ReactTestRenderer.create(
+
+
+
+ ,
+ {unstable_isConcurrent: true},
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
+ expect(callback).toHaveBeenCalledTimes(0);
- expect(callback).toHaveBeenCalledTimes(1);
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(100); // 15 -> 115
- let call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-mount');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(1010); // durations
- expect(call[3]).toBe(2); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- call = callback.mock.calls[1];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-mount');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(110); // durations
- expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- ReactTestRenderer.act(() => {
+ // Interrupt with higher priority work.
+ // The interrupted work simulates an additional 5ms of time.
+ renderer.unstable_flushSync(() => {
renderer.update(
-
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(3);
-
- call = callback.mock.calls[2];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-update');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(1100); // durations
- expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- it('should properly report time in layout effects even when there are errors', () => {
- const callback = jest.fn();
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- static getDerivedStateFromError(error) {
- return {error};
- }
- render() {
- return this.state.error === null
- ? this.props.children
- : this.props.fallback;
- }
- }
-
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- effectDuration,
- shouldThrow,
- }) => {
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(effectDuration);
- if (shouldThrow) {
- throw Error('expected');
- }
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- };
- });
- Scheduler.unstable_advanceTime(duration);
- return null;
- };
-
- Scheduler.unstable_advanceTime(1);
-
- // Test an error that happens during an effect
-
- ReactTestRenderer.act(() => {
- ReactTestRenderer.create(
-
-
- }>
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- let call = callback.mock.calls[0];
-
- // Initial render (with error)
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(100100); // durations
- expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- call = callback.mock.calls[1];
-
- // Cleanup render from error boundary
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(100000000); // durations
- expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- it('should properly report time in layout effect cleanup functions even when there are errors', () => {
- const callback = jest.fn();
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- static getDerivedStateFromError(error) {
- return {error};
- }
- render() {
- return this.state.error === null
- ? this.props.children
- : this.props.fallback;
- }
- }
-
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- effectDuration,
- shouldThrow = false,
- }) => {
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(effectDuration);
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- if (shouldThrow) {
- throw Error('expected');
- }
- };
- });
- Scheduler.unstable_advanceTime(duration);
- return null;
- };
-
- Scheduler.unstable_advanceTime(1);
-
- let renderer = null;
-
- ReactTestRenderer.act(() => {
- renderer = ReactTestRenderer.create(
-
-
- }>
-
-
-
+
+
,
);
});
+ expect(Scheduler).toHaveYielded(['Yield:5']);
+ // The initial work was thrown away in this case,
+ // So the actual and base times should only include the final rendered tree times.
expect(callback).toHaveBeenCalledTimes(1);
+ const call = callback.mock.calls[0];
+ expect(call[2]).toBe(5); // actual time
+ expect(call[3]).toBe(5); // base time
+ expect(call[4]).toBe(115); // start time
+ expect(call[5]).toBe(120); // commit time
+ callback.mockReset();
+
+ // Verify no more unexpected callbacks from low priority work
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(callback).toHaveBeenCalledTimes(0);
+ });
+
+ it('should report the expected times when a high-priority update replaces a low-priority update', () => {
+ const callback = jest.fn();
+
+ const Yield = ({renderTime}) => {
+ Scheduler.unstable_advanceTime(renderTime);
+ Scheduler.unstable_yieldValue('Yield:' + renderTime);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+ ,
+ {unstable_isConcurrent: true},
+ );
+
+ // Render everything initially.
+ // This should take 21 seconds of actual and base time.
+ expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
+ expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
+ expect(call[2]).toBe(21); // actual time
+ expect(call[3]).toBe(21); // base time
+ expect(call[4]).toBe(5); // start time
+ expect(call[5]).toBe(26); // commit time
- // Initial render
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(100100); // durations
- expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ callback.mockReset();
+
+ Scheduler.unstable_advanceTime(30); // 26 -> 56
+
+ // Render a partially update, but don't finish.
+ // This partial render should take 3ms of simulated time.
+ renderer.update(
+
+
+
+
+ ,
+ );
+ expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
+ expect(callback).toHaveBeenCalledTimes(0);
+
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(100); // 59 -> 159
+
+ // Render another 5ms of simulated time.
+ expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
+ expect(callback).toHaveBeenCalledTimes(0);
+
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(100); // 164 -> 264
+
+ // Interrupt with higher priority work.
+ // The interrupted work simulates an additional 11ms of time.
+ renderer.unstable_flushSync(() => {
+ renderer.update(
+
+
+ ,
+ );
+ });
+ expect(Scheduler).toHaveYielded(['Yield:11']);
+
+ // The actual time should include only the most recent render,
+ // Because this lets us avoid a lot of commit phase reset complexity.
+ // The base time includes only the final rendered tree times.
+ expect(callback).toHaveBeenCalledTimes(1);
+ call = callback.mock.calls[0];
+ expect(call[2]).toBe(11); // actual time
+ expect(call[3]).toBe(11); // base time
+ expect(call[4]).toBe(264); // start time
+ expect(call[5]).toBe(275); // commit time
+
+ // Verify no more unexpected callbacks from low priority work
+ expect(Scheduler).toFlushAndYield([]);
+ expect(callback).toHaveBeenCalledTimes(1);
+ });
+
+ it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
+ const callback = jest.fn();
+
+ const Yield = ({renderTime}) => {
+ Scheduler.unstable_advanceTime(renderTime);
+ Scheduler.unstable_yieldValue('Yield:' + renderTime);
+ return null;
+ };
+
+ let first;
+ class FirstComponent extends React.Component {
+ state = {renderTime: 1};
+ render() {
+ first = this;
+ Scheduler.unstable_advanceTime(this.state.renderTime);
+ Scheduler.unstable_yieldValue(
+ 'FirstComponent:' + this.state.renderTime,
+ );
+ return ;
+ }
+ }
+ let second;
+ class SecondComponent extends React.Component {
+ state = {renderTime: 2};
+ render() {
+ second = this;
+ Scheduler.unstable_advanceTime(this.state.renderTime);
+ Scheduler.unstable_yieldValue(
+ 'SecondComponent:' + this.state.renderTime,
+ );
+ return ;
+ }
+ }
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+ ,
+ {unstable_isConcurrent: true},
+ );
+
+ // Render everything initially.
+ // This simulates a total of 14ms of actual render time.
+ // The base render time is also 14ms for the initial render.
+ expect(Scheduler).toFlushAndYield([
+ 'FirstComponent:1',
+ 'Yield:4',
+ 'SecondComponent:2',
+ 'Yield:7',
+ ]);
+ expect(callback).toHaveBeenCalledTimes(1);
+ let call = callback.mock.calls[0];
+ expect(call[2]).toBe(14); // actual time
+ expect(call[3]).toBe(14); // base time
+ expect(call[4]).toBe(5); // start time
+ expect(call[5]).toBe(19); // commit time
callback.mockClear();
- // Test an error that happens during an cleanup function
+ Scheduler.unstable_advanceTime(100); // 19 -> 119
- ReactTestRenderer.act(() => {
- renderer.update(
-
-
- }>
-
-
-
- ,
- );
- });
+ // Render a partially update, but don't finish.
+ // This partial render will take 10ms of actual render time.
+ first.setState({renderTime: 10});
+ expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
+ expect(callback).toHaveBeenCalledTimes(0);
- expect(callback).toHaveBeenCalledTimes(2);
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(100); // 129 -> 229
+ // Interrupt with higher priority work.
+ // This simulates a total of 37ms of actual render time.
+ renderer.unstable_flushSync(() => second.setState({renderTime: 30}));
+ expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
+
+ // The actual time should include only the most recent render (37ms),
+ // Because this greatly simplifies the commit phase logic.
+ // The base time should include the more recent times for the SecondComponent subtree,
+ // As well as the original times for the FirstComponent subtree.
+ expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
+ expect(call[2]).toBe(37); // actual time
+ expect(call[3]).toBe(42); // base time
+ expect(call[4]).toBe(229); // start time
+ expect(call[5]).toBe(266); // commit time
- // Update (that throws)
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(1101100); // durations
- expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ callback.mockClear();
- call = callback.mock.calls[1];
+ // Simulate time moving forward while frame is paused.
+ Scheduler.unstable_advanceTime(100); // 266 -> 366
- // Cleanup render from error boundary
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(100001000); // durations
- expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ // Resume the original low priority update, with rebased state.
+ // This simulates a total of 14ms of actual render time,
+ // And does not include the original (interrupted) 10ms.
+ // The tree contains 42ms of base render time at this point,
+ // Reflecting the most recent (longer) render durations.
+ // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
+ expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
+ expect(callback).toHaveBeenCalledTimes(1);
+ call = callback.mock.calls[0];
+ expect(call[2]).toBe(14); // actual time
+ expect(call[3]).toBe(51); // base time
+ expect(call[4]).toBe(366); // start time
+ expect(call[5]).toBe(380); // commit time
});
- if (enableSchedulerTracing) {
- it('should report interactions that were active', () => {
- const callback = jest.fn();
+ [true, false].forEach(
+ replayFailedUnitOfWorkWithInvokeGuardedCallback => {
+ describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
+ replayFailedUnitOfWorkWithInvokeGuardedCallback
+ ? 'enabled'
+ : 'disabled'
+ }`, () => {
+ beforeEach(() => {
+ jest.resetModules();
- const ComponetWithEffects = () => {
- const [didMount, setDidMount] = React.useState(false);
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
- if (!didMount) {
- setDidMount(true);
- }
- return () => {
- Scheduler.unstable_advanceTime(10000);
+ loadModules({
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
+ });
+ });
+
+ it('should accumulate actual time after an error handled by componentDidCatch()', () => {
+ const callback = jest.fn();
+
+ const ThrowsError = ({unused}) => {
+ Scheduler.unstable_advanceTime(3);
+ throw Error('expected error');
};
- }, [didMount]);
- Scheduler.unstable_advanceTime(10);
- return null;
- };
- const interaction = {
- id: 0,
- name: 'mount',
- timestamp: Scheduler.unstable_now(),
- };
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
+ }
+ render() {
+ Scheduler.unstable_advanceTime(2);
+ return this.state.error === null ? (
+ this.props.children
+ ) : (
+
+ );
+ }
+ }
- Scheduler.unstable_advanceTime(1);
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
- SchedulerTracing.unstable_trace(
- interaction.name,
- interaction.timestamp,
- () => {
ReactTestRenderer.create(
-
-
+
+
+
+
+
,
);
- },
- );
- expect(callback).toHaveBeenCalledTimes(2);
+ expect(callback).toHaveBeenCalledTimes(2);
- let call = callback.mock.calls[0];
+ // Callbacks bubble (reverse order).
+ const [mountCall, updateCall] = callback.mock.calls;
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[4]).toMatchInteractions([interaction]);
+ // The initial mount only includes the ErrorBoundary (which takes 2)
+ // But it spends time rendering all of the failed subtree also.
+ expect(mountCall[1]).toBe('mount');
+ // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
+ // We don't count the time spent in replaying the failed unit of work (ThrowsError)
+ expect(mountCall[2]).toBe(14);
+ // base time includes: 2 (ErrorBoundary)
+ // Since the tree is empty for the initial commit
+ expect(mountCall[3]).toBe(2);
+ // start time
+ expect(mountCall[4]).toBe(5);
+ // commit time: 5 initially + 14 of work
+ // Add an additional 3 (ThrowsError) if we replayed the failed work
+ expect(mountCall[5]).toBe(
+ __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
+ ? 22
+ : 19,
+ );
- call = callback.mock.calls[1];
+ // The update includes the ErrorBoundary and its fallback child
+ expect(updateCall[1]).toBe('update');
+ // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
+ expect(updateCall[2]).toBe(22);
+ // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
+ expect(updateCall[3]).toBe(22);
+ // start time
+ expect(updateCall[4]).toBe(
+ __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
+ ? 22
+ : 19,
+ );
+ // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
+ // Add an additional 3 (ThrowsError) if we replayed the failed work
+ expect(updateCall[5]).toBe(
+ __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
+ ? 44
+ : 41,
+ );
+ });
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[4]).toMatchInteractions([interaction]);
- });
- }
+ it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
+ const callback = jest.fn();
+
+ const ThrowsError = ({unused}) => {
+ Scheduler.unstable_advanceTime(10);
+ throw Error('expected error');
+ };
+
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ Scheduler.unstable_advanceTime(2);
+ return this.state.error === null ? (
+ this.props.children
+ ) : (
+
+ );
+ }
+ }
+
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ ReactTestRenderer.create(
+
+
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ // Callbacks bubble (reverse order).
+ const [mountCall] = callback.mock.calls;
+
+ // The initial mount includes the ErrorBoundary's error state,
+ // But it also spends actual time rendering UI that fails and isn't included.
+ expect(mountCall[1]).toBe('mount');
+ // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
+ // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
+ // We don't count the time spent in replaying the failed unit of work (ThrowsError)
+ expect(mountCall[2]).toBe(39);
+ // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
+ expect(mountCall[3]).toBe(22);
+ // start time
+ expect(mountCall[4]).toBe(5);
+ // commit time
+ expect(mountCall[5]).toBe(
+ __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
+ ? 54
+ : 44,
+ );
+ });
+
+ it('should reset the fiber stack correct after a "complete" phase error', () => {
+ jest.resetModules();
+
+ loadModules({
+ useNoopRenderer: true,
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
+ });
+
+ // Simulate a renderer error during the "complete" phase.
+ // This mimics behavior like React Native's View/Text nesting validation.
+ ReactNoop.render(
+
+ hi
+ ,
+ );
+ expect(Scheduler).toFlushAndThrow('Error in host config.');
+
+ // A similar case we've seen caused by an invariant in ReactDOM.
+ // It didn't reproduce without a host component inside.
+ ReactNoop.render(
+
+
+ hi
+
+ ,
+ );
+ expect(Scheduler).toFlushAndThrow('Error in host config.');
+
+ // So long as the profiler timer's fiber stack is reset correctly,
+ // Subsequent renders should not error.
+ ReactNoop.render(
+
+ hi
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ });
+ });
+ },
+ );
});
- describe(`onPostCommit enableSchedulerTracing:${
- enableSchedulerTracing ? 'enabled' : 'disabled'
- } deferPassiveEffectCleanupDuringUnmount:${
- deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
- }`, () => {
- beforeEach(() => {
- jest.resetModules();
+ it('reflects the most recently rendered id value', () => {
+ const callback = jest.fn();
- loadModules({
- deferPassiveEffectCleanupDuringUnmount,
- enableSchedulerTracing,
+ Scheduler.unstable_advanceTime(5); // 0 -> 5
+
+ const renderer = ReactTestRenderer.create(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ Scheduler.unstable_advanceTime(20); // 7 -> 27
+
+ renderer.update(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ const [mountCall, updateCall] = callback.mock.calls;
+
+ expect(mountCall[0]).toBe('one');
+ expect(mountCall[1]).toBe('mount');
+ expect(mountCall[2]).toBe(2); // actual time
+ expect(mountCall[3]).toBe(2); // base time
+ expect(mountCall[4]).toBe(5); // start time
+
+ expect(updateCall[0]).toBe('two');
+ expect(updateCall[1]).toBe('update');
+ expect(updateCall[2]).toBe(1); // actual time
+ expect(updateCall[3]).toBe(1); // base time
+ expect(updateCall[4]).toBe(27); // start time
+ });
+
+ it('should not be called until after mutations', () => {
+ let classComponentMounted = false;
+ const callback = jest.fn(
+ (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
+ // Don't call this hook until after mutations
+ expect(classComponentMounted).toBe(true);
+ // But the commit time should reflect pre-mutation
+ expect(commitTime).toBe(2);
+ },
+ );
+
+ class ClassComponent extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_advanceTime(5);
+ classComponentMounted = true;
+ }
+ render() {
+ Scheduler.unstable_advanceTime(2);
+ return null;
+ }
+ }
+
+ ReactTestRenderer.create(
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe(`onCommit enableSchedulerTracing:${
+ enableSchedulerTracing ? 'enabled' : 'disabled'
+ }`, () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ loadModules({
+ enableSchedulerTracing,
+ });
+ });
+
+ it('should report time spent in layout effects and commit lifecycles', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = () => {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(10);
+ return () => {
+ Scheduler.unstable_advanceTime(100);
+ };
+ }, []);
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(1000);
+ return () => {
+ Scheduler.unstable_advanceTime(10000);
+ };
});
+ React.useEffect(() => {
+ // This passive effect is here to verify that its time isn't reported.
+ Scheduler.unstable_advanceTime(5);
+ return () => {
+ Scheduler.unstable_advanceTime(7);
+ };
+ });
+ return null;
+ };
+
+ class ComponentWithCommitHooks extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_advanceTime(100000);
+ }
+ componentDidUpdate() {
+ Scheduler.unstable_advanceTime(1000000);
+ }
+ render() {
+ return null;
+ }
+ }
+
+ Scheduler.unstable_advanceTime(1);
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(101010); // durations
+ expect(call[3]).toBe(1); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ Scheduler.unstable_advanceTime(1);
+
+ renderer.update(
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('update-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(1011000); // durations
+ expect(call[3]).toBe(101017); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ Scheduler.unstable_advanceTime(1);
+
+ renderer.update(
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(3);
+
+ call = callback.mock.calls[2];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('unmount-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(10100); // durations
+ expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should report time spent in layout effects and commit lifecycles with cascading renders', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = ({shouldCascade}) => {
+ const [didCascade, setDidCascade] = React.useState(false);
+ React.useLayoutEffect(() => {
+ if (shouldCascade && !didCascade) {
+ setDidCascade(true);
+ }
+ Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
+ return () => {
+ Scheduler.unstable_advanceTime(100);
+ };
+ }, [didCascade, shouldCascade]);
+ return null;
+ };
+
+ class ComponentWithCommitHooks extends React.Component {
+ state = {
+ didCascade: false,
+ };
+ componentDidMount() {
+ Scheduler.unstable_advanceTime(1000);
+ }
+ componentDidUpdate() {
+ Scheduler.unstable_advanceTime(10000);
+ if (this.props.shouldCascade && !this.state.didCascade) {
+ this.setState({didCascade: true});
+ }
+ }
+ render() {
+ return null;
+ }
+ }
+
+ Scheduler.unstable_advanceTime(1);
+
+ const renderer = ReactTestRenderer.create(
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(1010); // durations
+ expect(call[3]).toBe(1); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(130); // durations
+ expect(call[3]).toBe(1011); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ Scheduler.unstable_advanceTime(1);
+
+ renderer.update(
+
+
+
+ ,
+ );
+
+ expect(callback).toHaveBeenCalledTimes(4);
+
+ call = callback.mock.calls[2];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('update-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(10130); // durations
+ expect(call[3]).toBe(1142); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[3];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('update-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(10000); // durations
+ expect(call[3]).toBe(11272); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should bubble time spent in layout effects to higher profilers', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ setCountRef,
+ }) => {
+ const setCount = React.useState(0)[1];
+ if (setCountRef != null) {
+ setCountRef.current = setCount;
+ }
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(duration);
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ };
+ });
+ Scheduler.unstable_advanceTime(1);
+ return null;
+ };
+
+ const setCountRef = React.createRef(null);
+
+ let renderer = null;
+ ReactTestRenderer.act(() => {
+ renderer = ReactTestRenderer.create(
+
+
+
+
+
+
+
+ ,
+ );
});
- it('should report time spent in passive effects', () => {
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-mount');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(1010); // durations
+ expect(call[3]).toBe(2); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-mount');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(110); // durations
+ expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(3);
+
+ call = callback.mock.calls[2];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-update');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(1100); // durations
+ expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should properly report time in layout effects even when there are errors', () => {
+ const callback = jest.fn();
+
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ return this.state.error === null
+ ? this.props.children
+ : this.props.fallback;
+ }
+ }
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ effectDuration,
+ shouldThrow,
+ }) => {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(effectDuration);
+ if (shouldThrow) {
+ throw Error('expected');
+ }
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ };
+ });
+ Scheduler.unstable_advanceTime(duration);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(1);
+
+ // Test an error that happens during an effect
+
+ ReactTestRenderer.act(() => {
+ ReactTestRenderer.create(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ let call = callback.mock.calls[0];
+
+ // Initial render (with error)
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(100100); // durations
+ expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ // Cleanup render from error boundary
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(100000000); // durations
+ expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should properly report time in layout effect cleanup functions even when there are errors', () => {
+ const callback = jest.fn();
+
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ return this.state.error === null
+ ? this.props.children
+ : this.props.fallback;
+ }
+ }
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ effectDuration,
+ shouldThrow = false,
+ }) => {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(effectDuration);
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ if (shouldThrow) {
+ throw Error('expected');
+ }
+ };
+ });
+ Scheduler.unstable_advanceTime(duration);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(1);
+
+ let renderer = null;
+
+ ReactTestRenderer.act(() => {
+ renderer = ReactTestRenderer.create(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ // Initial render
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(100100); // durations
+ expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockClear();
+
+ // Test an error that happens during an cleanup function
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[0];
+
+ // Update (that throws)
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(1101100); // durations
+ expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ // Cleanup render from error boundary
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(100001000); // durations
+ expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ if (enableSchedulerTracing) {
+ it('should report interactions that were active', () => {
const callback = jest.fn();
const ComponetWithEffects = () => {
+ const [didMount, setDidMount] = React.useState(false);
React.useLayoutEffect(() => {
- // This layout effect is here to verify that its time isn't reported.
- Scheduler.unstable_advanceTime(5);
- return () => {
- Scheduler.unstable_advanceTime(7);
- };
- });
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(10);
- return () => {
- Scheduler.unstable_advanceTime(100);
- };
- }, []);
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(1000);
+ Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
+ if (!didMount) {
+ setDidMount(true);
+ }
return () => {
Scheduler.unstable_advanceTime(10000);
};
- });
+ }, [didMount]);
+ Scheduler.unstable_advanceTime(10);
return null;
};
+ const interaction = {
+ id: 0,
+ name: 'mount',
+ timestamp: Scheduler.unstable_now(),
+ };
+
Scheduler.unstable_advanceTime(1);
- let renderer;
- ReactTestRenderer.act(() => {
- renderer = ReactTestRenderer.create(
-
-
- ,
- );
- });
- Scheduler.unstable_flushAll();
+ SchedulerTracing.unstable_trace(
+ interaction.name,
+ interaction.timestamp,
+ () => {
+ ReactTestRenderer.create(
+
+
+ ,
+ );
+ },
+ );
- expect(callback).toHaveBeenCalledTimes(1);
+ expect(callback).toHaveBeenCalledTimes(2);
let call = callback.mock.calls[0];
expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
+ expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
- expect(call[2]).toBe(1010); // durations
- expect(call[3]).toBe(1); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- Scheduler.unstable_advanceTime(1);
-
- ReactTestRenderer.act(() => {
- renderer.update(
-
-
- ,
- );
- });
- Scheduler.unstable_flushAll();
-
- expect(callback).toHaveBeenCalledTimes(2);
+ expect(call[4]).toMatchInteractions([interaction]);
call = callback.mock.calls[1];
expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('update-test');
+ expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
- expect(call[2]).toBe(11000); // durations
- expect(call[3]).toBe(1017); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ expect(call[4]).toMatchInteractions([interaction]);
+ });
+ }
+ });
- Scheduler.unstable_advanceTime(1);
+ describe(`onPostCommit enableSchedulerTracing:${
+ enableSchedulerTracing ? 'enabled' : 'disabled'
+ }`, () => {
+ beforeEach(() => {
+ jest.resetModules();
- ReactTestRenderer.act(() => {
- renderer.update(
- ,
- );
+ loadModules({
+ enableSchedulerTracing,
+ });
+ });
+
+ it('should report time spent in passive effects', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = () => {
+ React.useLayoutEffect(() => {
+ // This layout effect is here to verify that its time isn't reported.
+ Scheduler.unstable_advanceTime(5);
+ return () => {
+ Scheduler.unstable_advanceTime(7);
+ };
});
- Scheduler.unstable_flushAll();
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(10);
+ return () => {
+ Scheduler.unstable_advanceTime(100);
+ };
+ }, []);
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(1000);
+ return () => {
+ Scheduler.unstable_advanceTime(10000);
+ };
+ });
+ return null;
+ };
- expect(callback).toHaveBeenCalledTimes(3);
+ Scheduler.unstable_advanceTime(1);
- call = callback.mock.calls[2];
+ let renderer;
+ ReactTestRenderer.act(() => {
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ );
+ });
+ Scheduler.unstable_flushAll();
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('unmount-test');
- expect(call[1]).toBe('update');
- // TODO (bvaughn) The duration reported below should be 10100, but is 0
- // by the time the passive effect is flushed its parent Fiber pointer is gone.
- // If we refactor to preserve the unmounted Fiber tree we could fix this.
- // The current implementation would require too much extra overhead to track this.
- expect(call[2]).toBe(0); // durations
- expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(1010); // durations
+ expect(call[3]).toBe(1); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ Scheduler.unstable_advanceTime(1);
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+
+
+ ,
+ );
+ });
+ Scheduler.unstable_flushAll();
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('update-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(11000); // durations
+ expect(call[3]).toBe(1017); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ Scheduler.unstable_advanceTime(1);
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+ ,
+ );
+ });
+ Scheduler.unstable_flushAll();
+
+ expect(callback).toHaveBeenCalledTimes(3);
+
+ call = callback.mock.calls[2];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('unmount-test');
+ expect(call[1]).toBe('update');
+ // TODO (bvaughn) The duration reported below should be 10100, but is 0
+ // by the time the passive effect is flushed its parent Fiber pointer is gone.
+ // If we refactor to preserve the unmounted Fiber tree we could fix this.
+ // The current implementation would require too much extra overhead to track this.
+ expect(call[2]).toBe(0); // durations
+ expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should report time spent in passive effects with cascading renders', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = () => {
+ const [didMount, setDidMount] = React.useState(false);
+ React.useEffect(() => {
+ if (!didMount) {
+ setDidMount(true);
+ }
+ Scheduler.unstable_advanceTime(didMount ? 30 : 10);
+ return () => {
+ Scheduler.unstable_advanceTime(100);
+ };
+ }, [didMount]);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(1);
+
+ ReactTestRenderer.act(() => {
+ ReactTestRenderer.create(
+
+
+ ,
+ );
});
- it('should report time spent in passive effects with cascading renders', () => {
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(10); // durations
+ expect(call[3]).toBe(1); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('mount-test');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(130); // durations
+ expect(call[3]).toBe(11); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should bubble time spent in effects to higher profilers', () => {
+ const callback = jest.fn();
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ setCountRef,
+ }) => {
+ const setCount = React.useState(0)[1];
+ if (setCountRef != null) {
+ setCountRef.current = setCount;
+ }
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(duration);
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ };
+ });
+ Scheduler.unstable_advanceTime(1);
+ return null;
+ };
+
+ const setCountRef = React.createRef(null);
+
+ let renderer = null;
+ ReactTestRenderer.act(() => {
+ renderer = ReactTestRenderer.create(
+
+
+
+
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-mount');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(1010); // durations
+ expect(call[3]).toBe(2); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[1];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-mount');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(110); // durations
+ expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(3);
+
+ call = callback.mock.calls[2];
+
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root-update');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(1100); // durations
+ expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should properly report time in passive effects even when there are errors', () => {
+ const callback = jest.fn();
+
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ return this.state.error === null
+ ? this.props.children
+ : this.props.fallback;
+ }
+ }
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ effectDuration,
+ shouldThrow,
+ }) => {
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(effectDuration);
+ if (shouldThrow) {
+ throw Error('expected');
+ }
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ };
+ });
+ Scheduler.unstable_advanceTime(duration);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(1);
+
+ // Test an error that happens during an effect
+
+ ReactTestRenderer.act(() => {
+ ReactTestRenderer.create(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ let call = callback.mock.calls[0];
+
+ // Initial render (with error)
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(100100); // durations
+ expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ // Cleanup render from error boundary
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(100000000); // durations
+ expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ it('should properly report time in passive effect cleanup functions even when there are errors', () => {
+ const callback = jest.fn();
+
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ return this.state.error === null
+ ? this.props.children
+ : this.props.fallback;
+ }
+ }
+
+ const ComponetWithEffects = ({
+ cleanupDuration,
+ duration,
+ effectDuration,
+ shouldThrow = false,
+ id,
+ }) => {
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(effectDuration);
+ return () => {
+ Scheduler.unstable_advanceTime(cleanupDuration);
+ if (shouldThrow) {
+ throw Error('expected');
+ }
+ };
+ });
+ Scheduler.unstable_advanceTime(duration);
+ return null;
+ };
+
+ Scheduler.unstable_advanceTime(1);
+
+ let renderer = null;
+
+ ReactTestRenderer.act(() => {
+ renderer = ReactTestRenderer.create(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ let call = callback.mock.calls[0];
+
+ // Initial render
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('mount');
+ expect(call[2]).toBe(100100); // durations
+ expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ callback.mockClear();
+
+ // Test an error that happens during an cleanup function
+
+ ReactTestRenderer.act(() => {
+ renderer.update(
+
+
+ }>
+
+
+
+ ,
+ );
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+
+ call = callback.mock.calls[0];
+
+ // Update (that throws)
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ // We continue flushing pending effects even if one throws.
+ expect(call[2]).toBe(1101100); // durations
+ expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+
+ call = callback.mock.calls[1];
+
+ // Cleanup render from error boundary
+ expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
+ expect(call[0]).toBe('root');
+ expect(call[1]).toBe('update');
+ expect(call[2]).toBe(100000000); // durations
+ // The commit time varies because the above duration time varies
+ expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
+ expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
+ });
+
+ if (enableSchedulerTracing) {
+ it('should report interactions that were active', () => {
const callback = jest.fn();
const ComponetWithEffects = () => {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
+ Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
if (!didMount) {
setDidMount(true);
}
- Scheduler.unstable_advanceTime(didMount ? 30 : 10);
return () => {
- Scheduler.unstable_advanceTime(100);
+ Scheduler.unstable_advanceTime(10000);
};
}, [didMount]);
+ Scheduler.unstable_advanceTime(10);
return null;
};
+ const interaction = {
+ id: 0,
+ name: 'mount',
+ timestamp: Scheduler.unstable_now(),
+ };
+
Scheduler.unstable_advanceTime(1);
ReactTestRenderer.act(() => {
- ReactTestRenderer.create(
-
-
- ,
+ SchedulerTracing.unstable_trace(
+ interaction.name,
+ interaction.timestamp,
+ () => {
+ ReactTestRenderer.create(
+
+
+ ,
+ );
+ },
);
});
@@ -2060,422 +2358,18 @@ describe('Profiler', () => {
let call = callback.mock.calls[0];
expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
+ expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
- expect(call[2]).toBe(10); // durations
- expect(call[3]).toBe(1); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ expect(call[4]).toMatchInteractions([interaction]);
call = callback.mock.calls[1];
expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('mount-test');
+ expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
- expect(call[2]).toBe(130); // durations
- expect(call[3]).toBe(11); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
+ expect(call[4]).toMatchInteractions([interaction]);
});
-
- it('should bubble time spent in effects to higher profilers', () => {
- const callback = jest.fn();
-
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- setCountRef,
- }) => {
- const setCount = React.useState(0)[1];
- if (setCountRef != null) {
- setCountRef.current = setCount;
- }
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(duration);
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- };
- });
- Scheduler.unstable_advanceTime(1);
- return null;
- };
-
- const setCountRef = React.createRef(null);
-
- let renderer = null;
- ReactTestRenderer.act(() => {
- renderer = ReactTestRenderer.create(
-
-
-
-
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- let call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-mount');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(1010); // durations
- expect(call[3]).toBe(2); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- call = callback.mock.calls[1];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-mount');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(110); // durations
- expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- ReactTestRenderer.act(() => {
- renderer.update(
-
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(3);
-
- call = callback.mock.calls[2];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root-update');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(1100); // durations
- expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- it('should properly report time in passive effects even when there are errors', () => {
- const callback = jest.fn();
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- static getDerivedStateFromError(error) {
- return {error};
- }
- render() {
- return this.state.error === null
- ? this.props.children
- : this.props.fallback;
- }
- }
-
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- effectDuration,
- shouldThrow,
- }) => {
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(effectDuration);
- if (shouldThrow) {
- throw Error('expected');
- }
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- };
- });
- Scheduler.unstable_advanceTime(duration);
- return null;
- };
-
- Scheduler.unstable_advanceTime(1);
-
- // Test an error that happens during an effect
-
- ReactTestRenderer.act(() => {
- ReactTestRenderer.create(
-
-
- }>
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- let call = callback.mock.calls[0];
-
- // Initial render (with error)
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(100100); // durations
- expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- call = callback.mock.calls[1];
-
- // Cleanup render from error boundary
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(100000000); // durations
- expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- it('should properly report time in passive effect cleanup functions even when there are errors', () => {
- const callback = jest.fn();
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- static getDerivedStateFromError(error) {
- return {error};
- }
- render() {
- return this.state.error === null
- ? this.props.children
- : this.props.fallback;
- }
- }
-
- const ComponetWithEffects = ({
- cleanupDuration,
- duration,
- effectDuration,
- shouldThrow = false,
- id,
- }) => {
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(effectDuration);
- return () => {
- Scheduler.unstable_advanceTime(cleanupDuration);
- if (shouldThrow) {
- throw Error('expected');
- }
- };
- });
- Scheduler.unstable_advanceTime(duration);
- return null;
- };
-
- Scheduler.unstable_advanceTime(1);
-
- let renderer = null;
-
- ReactTestRenderer.act(() => {
- renderer = ReactTestRenderer.create(
-
-
- }>
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(1);
-
- let call = callback.mock.calls[0];
-
- // Initial render
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[2]).toBe(100100); // durations
- expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- callback.mockClear();
-
- // Test an error that happens during an cleanup function
-
- ReactTestRenderer.act(() => {
- renderer.update(
-
-
- }>
-
-
-
- ,
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- call = callback.mock.calls[0];
-
- // Update (that throws)
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- // The duration varies because the flushing behavior varies when this flag is on.
- // We continue flushing pending effects even if one throws.
- expect(call[2]).toBe(
- deferPassiveEffectCleanupDuringUnmount ? 1101100 : 1101000,
- ); // durations
- expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
-
- call = callback.mock.calls[1];
-
- // Cleanup render from error boundary
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[2]).toBe(100000000); // durations
- // The commit time varies because the above duration time varies
- expect(call[3]).toBe(
- deferPassiveEffectCleanupDuringUnmount ? 11221221 : 11221121,
- ); // commit start time (before mutations or effects)
- expect(call[4]).toEqual(
- enableSchedulerTracing ? new Set() : undefined,
- ); // interaction events
- });
-
- if (enableSchedulerTracing) {
- it('should report interactions that were active', () => {
- const callback = jest.fn();
-
- const ComponetWithEffects = () => {
- const [didMount, setDidMount] = React.useState(false);
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
- if (!didMount) {
- setDidMount(true);
- }
- return () => {
- Scheduler.unstable_advanceTime(10000);
- };
- }, [didMount]);
- Scheduler.unstable_advanceTime(10);
- return null;
- };
-
- const interaction = {
- id: 0,
- name: 'mount',
- timestamp: Scheduler.unstable_now(),
- };
-
- Scheduler.unstable_advanceTime(1);
-
- ReactTestRenderer.act(() => {
- SchedulerTracing.unstable_trace(
- interaction.name,
- interaction.timestamp,
- () => {
- ReactTestRenderer.create(
-
-
- ,
- );
- },
- );
- });
-
- expect(callback).toHaveBeenCalledTimes(2);
-
- let call = callback.mock.calls[0];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('mount');
- expect(call[4]).toMatchInteractions([interaction]);
-
- call = callback.mock.calls[1];
-
- expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
- expect(call[0]).toBe('root');
- expect(call[1]).toBe('update');
- expect(call[4]).toMatchInteractions([interaction]);
- });
- }
- });
+ }
});
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 300bb5c859..466491b5b4 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -82,21 +82,6 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
-// Controls sequence of passive effect destroy and create functions.
-// If this flag is off, destroy and create functions may be interleaved.
-// When the flag is on, all destroy functions will be run (for all fibers)
-// before any create functions are run, similar to how layout effects work.
-// This flag provides a killswitch if that proves to break existing code somehow.
-export const runAllPassiveEffectDestroysBeforeCreates = false;
-
-// Controls behavior of deferred effect destroy functions during unmount.
-// Previously these functions were run during commit (along with layout effects).
-// Ideally we should delay these until after commit for performance reasons.
-// This flag provides a killswitch if that proves to break existing code somehow.
-//
-// WARNING This flag only has an affect if used with runAllPassiveEffectDestroysBeforeCreates.
-export const deferPassiveEffectCleanupDuringUnmount = false;
-
// Enables a warning when trying to spread a 'key' to an element;
// a deprecated pattern we want to get rid of in the future
export const warnAboutSpreadingKeyToJSX = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 375e579762..f4277d4c40 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -38,8 +38,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = false;
-export const runAllPassiveEffectDestroysBeforeCreates = false;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index c0e300fa17..f71b664b35 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = false;
-export const runAllPassiveEffectDestroysBeforeCreates = false;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 5dfa2ebf3e..8d16a4ba10 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = false;
-export const runAllPassiveEffectDestroysBeforeCreates = false;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 16a1e8fabe..b2b014065e 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = true;
-export const runAllPassiveEffectDestroysBeforeCreates = true;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js
index b034a559fc..76667d09bb 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.js
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = false;
-export const runAllPassiveEffectDestroysBeforeCreates = false;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js
index ae2ad698c3..5dd0f5b420 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = __EXPERIMENTAL__;
export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
-export const deferPassiveEffectCleanupDuringUnmount = true;
-export const runAllPassiveEffectDestroysBeforeCreates = true;
export const enableModernEventSystem = false;
export const warnAboutSpreadingKeyToJSX = false;
export const enableComponentStackLocations = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index b9ee3f0408..82c05a8acc 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -76,9 +76,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
// to the correct value.
export const enableNewReconciler = __VARIANT__;
-export const deferPassiveEffectCleanupDuringUnmount = true;
-export const runAllPassiveEffectDestroysBeforeCreates = true;
-
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y = _X> = null;