@samitouri / QOS-React-2 / commits / 5d1ce65139

Align StrictMode behaviour with production (#25049)

* Skip double invoking effects in Offscreen * Run yarn replace-fork * Use executionContext to disable profiler timer * Restructure recursion into two functions * Fix ReactStrictMode test * Use gate pragma in ReacetOffscreenStrictMode test * Set and reset current debug fiber in dev * Skip over paths that don't include any insertions * Extract common logic to check for profiling to a helper function * Remove hasPassiveEffects flag from StrictMode * Fix flow issues * Revert "Skip over paths that don't include any insertions"

Samuel Susla committed Aug 23, 2022 at 18:19 UTC 5d1ce651393524639a3b4b1e861a0413a4d25629
10 files changed +376 -557
packages/react-reconciler/src/ReactChildFiber.new.js
+9 -4
@@ -13,7 +13,12 @@ import type {Fiber} from './ReactInternalTypes';
13 import type {Lanes} from './ReactFiberLane.new';
14
15 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
16 -import {Placement, ChildDeletion, Forked} from './ReactFiberFlags';
16 +import {
17 + Placement,
18 + ChildDeletion,
19 + Forked,
20 + PlacementDEV,
21 +} from './ReactFiberFlags';
22 import {
23 getIteratorFn,
24 REACT_ELEMENT_TYPE,
@@ -343,7 +348,7 @@ function ChildReconciler(shouldTrackSideEffects) {
348 const oldIndex = current.index;
349 if (oldIndex < lastPlacedIndex) {
350 // This is a move.
346 - newFiber.flags |= Placement;
351 + newFiber.flags |= Placement | PlacementDEV;
352 return lastPlacedIndex;
353 } else {
354 // This item can stay in place.
@@ -351,7 +356,7 @@ function ChildReconciler(shouldTrackSideEffects) {
356 }
357 } else {
358 // This is an insertion.
354 - newFiber.flags |= Placement;
359 + newFiber.flags |= Placement | PlacementDEV;
360 return lastPlacedIndex;
361 }
362 }
@@ -360,7 +365,7 @@ function ChildReconciler(shouldTrackSideEffects) {
365 // This is simpler for the single child case. We only need to do a
366 // placement for inserting new children.
367 if (shouldTrackSideEffects && newFiber.alternate === null) {
363 - newFiber.flags |= Placement;
368 + newFiber.flags |= Placement | PlacementDEV;
369 }
370 return newFiber;
371 }
packages/react-reconciler/src/ReactChildFiber.old.js
+9 -4
@@ -13,7 +13,12 @@ import type {Fiber} from './ReactInternalTypes';
13 import type {Lanes} from './ReactFiberLane.old';
14
15 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
16 -import {Placement, ChildDeletion, Forked} from './ReactFiberFlags';
16 +import {
17 + Placement,
18 + ChildDeletion,
19 + Forked,
20 + PlacementDEV,
21 +} from './ReactFiberFlags';
22 import {
23 getIteratorFn,
24 REACT_ELEMENT_TYPE,
@@ -343,7 +348,7 @@ function ChildReconciler(shouldTrackSideEffects) {
348 const oldIndex = current.index;
349 if (oldIndex < lastPlacedIndex) {
350 // This is a move.
346 - newFiber.flags |= Placement;
351 + newFiber.flags |= Placement | PlacementDEV;
352 return lastPlacedIndex;
353 } else {
354 // This item can stay in place.
@@ -351,7 +356,7 @@ function ChildReconciler(shouldTrackSideEffects) {
356 }
357 } else {
358 // This is an insertion.
354 - newFiber.flags |= Placement;
359 + newFiber.flags |= Placement | PlacementDEV;
360 return lastPlacedIndex;
361 }
362 }
@@ -360,7 +365,7 @@ function ChildReconciler(shouldTrackSideEffects) {
365 // This is simpler for the single child case. We only need to do a
366 // placement for inserting new children.
367 if (shouldTrackSideEffects && newFiber.alternate === null) {
363 - newFiber.flags |= Placement;
368 + newFiber.flags |= Placement | PlacementDEV;
369 }
370 return newFiber;
371 }
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+35 -176
@@ -40,7 +40,6 @@ import {
40 enableSchedulingProfiler,
41 enableSuspenseCallback,
42 enableScopeAPI,
43 - enableStrictEffects,
43 deletedTreeCleanUpLevel,
44 enableUpdaterTracking,
45 enableCache,
@@ -148,6 +147,9 @@ import {
147 addMarkerProgressCallbackToPendingTransition,
148 addMarkerCompleteCallbackToPendingTransition,
149 setIsRunningInsertionEffect,
150 + getExecutionContext,
151 + CommitContext,
152 + NoContext,
153 } from './ReactFiberWorkLoop.new';
154 import {
155 NoFlags as NoHookEffect,
@@ -200,6 +202,15 @@ let nextEffect: Fiber | null = null;
202 let inProgressLanes: Lanes | null = null;
203 let inProgressRoot: FiberRoot | null = null;
204
205 +function shouldProfile(current: Fiber): boolean {
206 + return (
207 + enableProfilerTimer &&
208 + enableProfilerCommitHooks &&
209 + (current.mode & ProfileMode) !== NoMode &&
210 + (getExecutionContext() & CommitContext) !== NoContext
211 + );
212 +}
213 +
214 export function reportUncaughtErrorInDEV(error: mixed) {
215 // Wrapping each small part of the commit phase into a guarded
216 // callback is a bit too slow (https://github.com/facebook/react/pull/21666).
@@ -217,11 +228,7 @@ export function reportUncaughtErrorInDEV(error: mixed) {
228 const callComponentWillUnmountWithTimer = function(current, instance) {
229 instance.props = current.memoizedProps;
230 instance.state = current.memoizedState;
220 - if (
221 - enableProfilerTimer &&
222 - enableProfilerCommitHooks &&
223 - current.mode & ProfileMode
224 - ) {
231 + if (shouldProfile(current)) {
232 try {
233 startLayoutEffectTimer();
234 instance.componentWillUnmount();
@@ -261,11 +268,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
268 if (typeof ref === 'function') {
269 let retVal;
270 try {
264 - if (
265 - enableProfilerTimer &&
266 - enableProfilerCommitHooks &&
267 - current.mode & ProfileMode
268 - ) {
271 + if (shouldProfile(current)) {
272 try {
273 startLayoutEffectTimer();
274 retVal = ref(null);
@@ -641,7 +644,11 @@ export function commitPassiveEffectDurations(
644 finishedRoot: FiberRoot,
645 finishedWork: Fiber,
646 ): void {
644 - if (enableProfilerTimer && enableProfilerCommitHooks) {
647 + if (
648 + enableProfilerTimer &&
649 + enableProfilerCommitHooks &&
650 + getExecutionContext() & CommitContext
651 + ) {
652 // Only Profilers with work in their subtree will have an Update effect scheduled.
653 if ((finishedWork.flags & Update) !== NoFlags) {
654 switch (finishedWork.tag) {
@@ -694,11 +701,7 @@ function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
701 // This is done to prevent sibling component effects from interfering with each other,
702 // e.g. a destroy function in one component should never override a ref set
703 // by a create function in another component during the same commit.
697 - if (
698 - enableProfilerTimer &&
699 - enableProfilerCommitHooks &&
700 - finishedWork.mode & ProfileMode
701 - ) {
704 + if (shouldProfile(finishedWork)) {
705 try {
706 startLayoutEffectTimer();
707 commitHookEffectListMount(hookFlags, finishedWork);
@@ -751,11 +754,7 @@ function commitClassLayoutLifecycles(
754 }
755 }
756 }
754 - if (
755 - enableProfilerTimer &&
756 - enableProfilerCommitHooks &&
757 - finishedWork.mode & ProfileMode
758 - ) {
757 + if (shouldProfile(finishedWork)) {
758 try {
759 startLayoutEffectTimer();
760 instance.componentDidMount();
@@ -806,11 +805,7 @@ function commitClassLayoutLifecycles(
805 }
806 }
807 }
809 - if (
810 - enableProfilerTimer &&
811 - enableProfilerCommitHooks &&
812 - finishedWork.mode & ProfileMode
813 - ) {
808 + if (shouldProfile(finishedWork)) {
809 try {
810 startLayoutEffectTimer();
811 instance.componentDidUpdate(
@@ -892,7 +887,7 @@ function commitHostComponentMount(finishedWork: Fiber) {
887 }
888
889 function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
895 - if (enableProfilerTimer) {
890 + if (enableProfilerTimer && getExecutionContext() & CommitContext) {
891 try {
892 const {onCommit, onRender} = finishedWork.memoizedProps;
893 const {effectDuration} = finishedWork.stateNode;
@@ -1341,11 +1336,7 @@ function commitAttachRef(finishedWork: Fiber) {
1336 }
1337 if (typeof ref === 'function') {
1338 let retVal;
1344 - if (
1345 - enableProfilerTimer &&
1346 - enableProfilerCommitHooks &&
1347 - finishedWork.mode & ProfileMode
1348 - ) {
1339 + if (shouldProfile(finishedWork)) {
1340 try {
1341 startLayoutEffectTimer();
1342 retVal = ref(instanceToUse);
@@ -1384,11 +1375,7 @@ function commitDetachRef(current: Fiber) {
1375 const currentRef = current.ref;
1376 if (currentRef !== null) {
1377 if (typeof currentRef === 'function') {
1387 - if (
1388 - enableProfilerTimer &&
1389 - enableProfilerCommitHooks &&
1390 - current.mode & ProfileMode
1391 - ) {
1378 + if (shouldProfile(current)) {
1379 try {
1380 startLayoutEffectTimer();
1381 currentRef(null);
@@ -1914,11 +1901,7 @@ function commitDeletionEffectsOnFiber(
1901 markComponentLayoutEffectUnmountStarted(deletedFiber);
1902 }
1903
1917 - if (
1918 - enableProfilerTimer &&
1919 - enableProfilerCommitHooks &&
1920 - deletedFiber.mode & ProfileMode
1921 - ) {
1904 + if (shouldProfile(deletedFiber)) {
1905 startLayoutEffectTimer();
1906 safelyCallDestroy(
1907 deletedFiber,
@@ -2237,11 +2220,7 @@ function commitMutationEffectsOnFiber(
2220 // This prevents sibling component effects from interfering with each other,
2221 // e.g. a destroy function in one component should never override a ref set
2222 // by a create function in another component during the same commit.
2240 - if (
2241 - enableProfilerTimer &&
2242 - enableProfilerCommitHooks &&
2243 - finishedWork.mode & ProfileMode
2244 - ) {
2223 + if (shouldProfile(finishedWork)) {
2224 try {
2225 startLayoutEffectTimer();
2226 commitHookEffectListUnmount(
@@ -2627,18 +2606,14 @@ function recursivelyTraverseLayoutEffects(
2606 setCurrentDebugFiberInDEV(prevDebugFiber);
2607 }
2608
2630 -function disappearLayoutEffects(finishedWork: Fiber) {
2609 +export function disappearLayoutEffects(finishedWork: Fiber) {
2610 switch (finishedWork.tag) {
2611 case FunctionComponent:
2612 case ForwardRef:
2613 case MemoComponent:
2614 case SimpleMemoComponent: {
2615 // TODO (Offscreen) Check: flags & LayoutStatic
2637 - if (
2638 - enableProfilerTimer &&
2639 - enableProfilerCommitHooks &&
2640 - finishedWork.mode & ProfileMode
2641 - ) {
2616 + if (shouldProfile(finishedWork)) {
2617 try {
2618 startLayoutEffectTimer();
2619 commitHookEffectListUnmount(
@@ -2709,7 +2684,7 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2684 }
2685 }
2686
2712 -function reappearLayoutEffects(
2687 +export function reappearLayoutEffects(
2688 finishedRoot: FiberRoot,
2689 current: Fiber | null,
2690 finishedWork: Fiber,
@@ -2876,11 +2851,7 @@ function commitHookPassiveMountEffects(
2851 finishedWork: Fiber,
2852 hookFlags: HookFlags,
2853 ) {
2879 - if (
2880 - enableProfilerTimer &&
2881 - enableProfilerCommitHooks &&
2882 - finishedWork.mode & ProfileMode
2883 - ) {
2854 + if (shouldProfile(finishedWork)) {
2855 startPassiveEffectTimer();
2856 try {
2857 commitHookEffectListMount(hookFlags, finishedWork);
@@ -3304,7 +3275,7 @@ function recursivelyTraverseReconnectPassiveEffects(
3275 setCurrentDebugFiberInDEV(prevDebugFiber);
3276 }
3277
3307 -function reconnectPassiveEffects(
3278 +export function reconnectPassiveEffects(
3279 finishedRoot: FiberRoot,
3280 finishedWork: Fiber,
3281 committedLanes: Lanes,
@@ -3585,11 +3556,7 @@ function commitHookPassiveUnmountEffects(
3556 nearestMountedAncestor,
3557 hookFlags: HookFlags,
3558 ) {
3588 - if (
3589 - enableProfilerTimer &&
3590 - enableProfilerCommitHooks &&
3591 - finishedWork.mode & ProfileMode
3592 - ) {
3559 + if (shouldProfile(finishedWork)) {
3560 startPassiveEffectTimer();
3561 commitHookEffectListUnmount(
3562 hookFlags,
@@ -3718,7 +3685,7 @@ function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
3685 setCurrentDebugFiberInDEV(prevDebugFiber);
3686 }
3687
3721 -function disconnectPassiveEffect(finishedWork: Fiber): void {
3688 +export function disconnectPassiveEffect(finishedWork: Fiber): void {
3689 switch (finishedWork.tag) {
3690 case FunctionComponent:
3691 case ForwardRef:
@@ -3870,112 +3837,4 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3837 }
3838 }
3839
3873 -// TODO: Reuse reappearLayoutEffects traversal here?
3874 -function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3875 - if (__DEV__ && enableStrictEffects) {
3876 - // We don't need to re-check StrictEffectsMode here.
3877 - // This function is only called if that check has already passed.
3878 - switch (fiber.tag) {
3879 - case FunctionComponent:
3880 - case ForwardRef:
3881 - case SimpleMemoComponent: {
3882 - try {
3883 - commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
3884 - } catch (error) {
3885 - captureCommitPhaseError(fiber, fiber.return, error);
3886 - }
3887 - break;
3888 - }
3889 - case ClassComponent: {
3890 - const instance = fiber.stateNode;
3891 - try {
3892 - instance.componentDidMount();
3893 - } catch (error) {
3894 - captureCommitPhaseError(fiber, fiber.return, error);
3895 - }
3896 - break;
3897 - }
3898 - }
3899 - }
3900 -}
3901 -
3902 -function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3903 - if (__DEV__ && enableStrictEffects) {
3904 - // We don't need to re-check StrictEffectsMode here.
3905 - // This function is only called if that check has already passed.
3906 - switch (fiber.tag) {
3907 - case FunctionComponent:
3908 - case ForwardRef:
3909 - case SimpleMemoComponent: {
3910 - try {
3911 - commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
3912 - } catch (error) {
3913 - captureCommitPhaseError(fiber, fiber.return, error);
3914 - }
3915 - break;
3916 - }
3917 - }
3918 - }
3919 -}
3920 -
3921 -function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3922 - if (__DEV__ && enableStrictEffects) {
3923 - // We don't need to re-check StrictEffectsMode here.
3924 - // This function is only called if that check has already passed.
3925 - switch (fiber.tag) {
3926 - case FunctionComponent:
3927 - case ForwardRef:
3928 - case SimpleMemoComponent: {
3929 - try {
3930 - commitHookEffectListUnmount(
3931 - HookLayout | HookHasEffect,
3932 - fiber,
3933 - fiber.return,
3934 - );
3935 - } catch (error) {
3936 - captureCommitPhaseError(fiber, fiber.return, error);
3937 - }
3938 - break;
3939 - }
3940 - case ClassComponent: {
3941 - const instance = fiber.stateNode;
3942 - if (typeof instance.componentWillUnmount === 'function') {
3943 - safelyCallComponentWillUnmount(fiber, fiber.return, instance);
3944 - }
3945 - break;
3946 - }
3947 - }
3948 - }
3949 -}
3950 -
3951 -function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3952 - if (__DEV__ && enableStrictEffects) {
3953 - // We don't need to re-check StrictEffectsMode here.
3954 - // This function is only called if that check has already passed.
3955 - switch (fiber.tag) {
3956 - case FunctionComponent:
3957 - case ForwardRef:
3958 - case SimpleMemoComponent: {
3959 - try {
3960 - commitHookEffectListUnmount(
3961 - HookPassive | HookHasEffect,
3962 - fiber,
3963 - fiber.return,
3964 - );
3965 - } catch (error) {
3966 - captureCommitPhaseError(fiber, fiber.return, error);
3967 - }
3968 - }
3969 - }
3970 - }
3971 -}
3972 -
3973 -export {
3974 - commitPlacement,
3975 - commitAttachRef,
3976 - commitDetachRef,
3977 - invokeLayoutEffectMountInDEV,
3978 - invokeLayoutEffectUnmountInDEV,
3979 - invokePassiveEffectMountInDEV,
3980 - invokePassiveEffectUnmountInDEV,
3981 -};
3840 +export {commitPlacement, commitAttachRef, commitDetachRef};
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+35 -176
@@ -40,7 +40,6 @@ import {
40 enableSchedulingProfiler,
41 enableSuspenseCallback,
42 enableScopeAPI,
43 - enableStrictEffects,
43 deletedTreeCleanUpLevel,
44 enableUpdaterTracking,
45 enableCache,
@@ -148,6 +147,9 @@ import {
147 addMarkerProgressCallbackToPendingTransition,
148 addMarkerCompleteCallbackToPendingTransition,
149 setIsRunningInsertionEffect,
150 + getExecutionContext,
151 + CommitContext,
152 + NoContext,
153 } from './ReactFiberWorkLoop.old';
154 import {
155 NoFlags as NoHookEffect,
@@ -200,6 +202,15 @@ let nextEffect: Fiber | null = null;
202 let inProgressLanes: Lanes | null = null;
203 let inProgressRoot: FiberRoot | null = null;
204
205 +function shouldProfile(current: Fiber): boolean {
206 + return (
207 + enableProfilerTimer &&
208 + enableProfilerCommitHooks &&
209 + (current.mode & ProfileMode) !== NoMode &&
210 + (getExecutionContext() & CommitContext) !== NoContext
211 + );
212 +}
213 +
214 export function reportUncaughtErrorInDEV(error: mixed) {
215 // Wrapping each small part of the commit phase into a guarded
216 // callback is a bit too slow (https://github.com/facebook/react/pull/21666).
@@ -217,11 +228,7 @@ export function reportUncaughtErrorInDEV(error: mixed) {
228 const callComponentWillUnmountWithTimer = function(current, instance) {
229 instance.props = current.memoizedProps;
230 instance.state = current.memoizedState;
220 - if (
221 - enableProfilerTimer &&
222 - enableProfilerCommitHooks &&
223 - current.mode & ProfileMode
224 - ) {
231 + if (shouldProfile(current)) {
232 try {
233 startLayoutEffectTimer();
234 instance.componentWillUnmount();
@@ -261,11 +268,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
268 if (typeof ref === 'function') {
269 let retVal;
270 try {
264 - if (
265 - enableProfilerTimer &&
266 - enableProfilerCommitHooks &&
267 - current.mode & ProfileMode
268 - ) {
271 + if (shouldProfile(current)) {
272 try {
273 startLayoutEffectTimer();
274 retVal = ref(null);
@@ -641,7 +644,11 @@ export function commitPassiveEffectDurations(
644 finishedRoot: FiberRoot,
645 finishedWork: Fiber,
646 ): void {
644 - if (enableProfilerTimer && enableProfilerCommitHooks) {
647 + if (
648 + enableProfilerTimer &&
649 + enableProfilerCommitHooks &&
650 + getExecutionContext() & CommitContext
651 + ) {
652 // Only Profilers with work in their subtree will have an Update effect scheduled.
653 if ((finishedWork.flags & Update) !== NoFlags) {
654 switch (finishedWork.tag) {
@@ -694,11 +701,7 @@ function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
701 // This is done to prevent sibling component effects from interfering with each other,
702 // e.g. a destroy function in one component should never override a ref set
703 // by a create function in another component during the same commit.
697 - if (
698 - enableProfilerTimer &&
699 - enableProfilerCommitHooks &&
700 - finishedWork.mode & ProfileMode
701 - ) {
704 + if (shouldProfile(finishedWork)) {
705 try {
706 startLayoutEffectTimer();
707 commitHookEffectListMount(hookFlags, finishedWork);
@@ -751,11 +754,7 @@ function commitClassLayoutLifecycles(
754 }
755 }
756 }
754 - if (
755 - enableProfilerTimer &&
756 - enableProfilerCommitHooks &&
757 - finishedWork.mode & ProfileMode
758 - ) {
757 + if (shouldProfile(finishedWork)) {
758 try {
759 startLayoutEffectTimer();
760 instance.componentDidMount();
@@ -806,11 +805,7 @@ function commitClassLayoutLifecycles(
805 }
806 }
807 }
809 - if (
810 - enableProfilerTimer &&
811 - enableProfilerCommitHooks &&
812 - finishedWork.mode & ProfileMode
813 - ) {
808 + if (shouldProfile(finishedWork)) {
809 try {
810 startLayoutEffectTimer();
811 instance.componentDidUpdate(
@@ -892,7 +887,7 @@ function commitHostComponentMount(finishedWork: Fiber) {
887 }
888
889 function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
895 - if (enableProfilerTimer) {
890 + if (enableProfilerTimer && getExecutionContext() & CommitContext) {
891 try {
892 const {onCommit, onRender} = finishedWork.memoizedProps;
893 const {effectDuration} = finishedWork.stateNode;
@@ -1341,11 +1336,7 @@ function commitAttachRef(finishedWork: Fiber) {
1336 }
1337 if (typeof ref === 'function') {
1338 let retVal;
1344 - if (
1345 - enableProfilerTimer &&
1346 - enableProfilerCommitHooks &&
1347 - finishedWork.mode & ProfileMode
1348 - ) {
1339 + if (shouldProfile(finishedWork)) {
1340 try {
1341 startLayoutEffectTimer();
1342 retVal = ref(instanceToUse);
@@ -1384,11 +1375,7 @@ function commitDetachRef(current: Fiber) {
1375 const currentRef = current.ref;
1376 if (currentRef !== null) {
1377 if (typeof currentRef === 'function') {
1387 - if (
1388 - enableProfilerTimer &&
1389 - enableProfilerCommitHooks &&
1390 - current.mode & ProfileMode
1391 - ) {
1378 + if (shouldProfile(current)) {
1379 try {
1380 startLayoutEffectTimer();
1381 currentRef(null);
@@ -1914,11 +1901,7 @@ function commitDeletionEffectsOnFiber(
1901 markComponentLayoutEffectUnmountStarted(deletedFiber);
1902 }
1903
1917 - if (
1918 - enableProfilerTimer &&
1919 - enableProfilerCommitHooks &&
1920 - deletedFiber.mode & ProfileMode
1921 - ) {
1904 + if (shouldProfile(deletedFiber)) {
1905 startLayoutEffectTimer();
1906 safelyCallDestroy(
1907 deletedFiber,
@@ -2237,11 +2220,7 @@ function commitMutationEffectsOnFiber(
2220 // This prevents sibling component effects from interfering with each other,
2221 // e.g. a destroy function in one component should never override a ref set
2222 // by a create function in another component during the same commit.
2240 - if (
2241 - enableProfilerTimer &&
2242 - enableProfilerCommitHooks &&
2243 - finishedWork.mode & ProfileMode
2244 - ) {
2223 + if (shouldProfile(finishedWork)) {
2224 try {
2225 startLayoutEffectTimer();
2226 commitHookEffectListUnmount(
@@ -2627,18 +2606,14 @@ function recursivelyTraverseLayoutEffects(
2606 setCurrentDebugFiberInDEV(prevDebugFiber);
2607 }
2608
2630 -function disappearLayoutEffects(finishedWork: Fiber) {
2609 +export function disappearLayoutEffects(finishedWork: Fiber) {
2610 switch (finishedWork.tag) {
2611 case FunctionComponent:
2612 case ForwardRef:
2613 case MemoComponent:
2614 case SimpleMemoComponent: {
2615 // TODO (Offscreen) Check: flags & LayoutStatic
2637 - if (
2638 - enableProfilerTimer &&
2639 - enableProfilerCommitHooks &&
2640 - finishedWork.mode & ProfileMode
2641 - ) {
2616 + if (shouldProfile(finishedWork)) {
2617 try {
2618 startLayoutEffectTimer();
2619 commitHookEffectListUnmount(
@@ -2709,7 +2684,7 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2684 }
2685 }
2686
2712 -function reappearLayoutEffects(
2687 +export function reappearLayoutEffects(
2688 finishedRoot: FiberRoot,
2689 current: Fiber | null,
2690 finishedWork: Fiber,
@@ -2876,11 +2851,7 @@ function commitHookPassiveMountEffects(
2851 finishedWork: Fiber,
2852 hookFlags: HookFlags,
2853 ) {
2879 - if (
2880 - enableProfilerTimer &&
2881 - enableProfilerCommitHooks &&
2882 - finishedWork.mode & ProfileMode
2883 - ) {
2854 + if (shouldProfile(finishedWork)) {
2855 startPassiveEffectTimer();
2856 try {
2857 commitHookEffectListMount(hookFlags, finishedWork);
@@ -3304,7 +3275,7 @@ function recursivelyTraverseReconnectPassiveEffects(
3275 setCurrentDebugFiberInDEV(prevDebugFiber);
3276 }
3277
3307 -function reconnectPassiveEffects(
3278 +export function reconnectPassiveEffects(
3279 finishedRoot: FiberRoot,
3280 finishedWork: Fiber,
3281 committedLanes: Lanes,
@@ -3585,11 +3556,7 @@ function commitHookPassiveUnmountEffects(
3556 nearestMountedAncestor,
3557 hookFlags: HookFlags,
3558 ) {
3588 - if (
3589 - enableProfilerTimer &&
3590 - enableProfilerCommitHooks &&
3591 - finishedWork.mode & ProfileMode
3592 - ) {
3559 + if (shouldProfile(finishedWork)) {
3560 startPassiveEffectTimer();
3561 commitHookEffectListUnmount(
3562 hookFlags,
@@ -3718,7 +3685,7 @@ function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
3685 setCurrentDebugFiberInDEV(prevDebugFiber);
3686 }
3687
3721 -function disconnectPassiveEffect(finishedWork: Fiber): void {
3688 +export function disconnectPassiveEffect(finishedWork: Fiber): void {
3689 switch (finishedWork.tag) {
3690 case FunctionComponent:
3691 case ForwardRef:
@@ -3870,112 +3837,4 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3837 }
3838 }
3839
3873 -// TODO: Reuse reappearLayoutEffects traversal here?
3874 -function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3875 - if (__DEV__ && enableStrictEffects) {
3876 - // We don't need to re-check StrictEffectsMode here.
3877 - // This function is only called if that check has already passed.
3878 - switch (fiber.tag) {
3879 - case FunctionComponent:
3880 - case ForwardRef:
3881 - case SimpleMemoComponent: {
3882 - try {
3883 - commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
3884 - } catch (error) {
3885 - captureCommitPhaseError(fiber, fiber.return, error);
3886 - }
3887 - break;
3888 - }
3889 - case ClassComponent: {
3890 - const instance = fiber.stateNode;
3891 - try {
3892 - instance.componentDidMount();
3893 - } catch (error) {
3894 - captureCommitPhaseError(fiber, fiber.return, error);
3895 - }
3896 - break;
3897 - }
3898 - }
3899 - }
3900 -}
3901 -
3902 -function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3903 - if (__DEV__ && enableStrictEffects) {
3904 - // We don't need to re-check StrictEffectsMode here.
3905 - // This function is only called if that check has already passed.
3906 - switch (fiber.tag) {
3907 - case FunctionComponent:
3908 - case ForwardRef:
3909 - case SimpleMemoComponent: {
3910 - try {
3911 - commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
3912 - } catch (error) {
3913 - captureCommitPhaseError(fiber, fiber.return, error);
3914 - }
3915 - break;
3916 - }
3917 - }
3918 - }
3919 -}
3920 -
3921 -function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3922 - if (__DEV__ && enableStrictEffects) {
3923 - // We don't need to re-check StrictEffectsMode here.
3924 - // This function is only called if that check has already passed.
3925 - switch (fiber.tag) {
3926 - case FunctionComponent:
3927 - case ForwardRef:
3928 - case SimpleMemoComponent: {
3929 - try {
3930 - commitHookEffectListUnmount(
3931 - HookLayout | HookHasEffect,
3932 - fiber,
3933 - fiber.return,
3934 - );
3935 - } catch (error) {
3936 - captureCommitPhaseError(fiber, fiber.return, error);
3937 - }
3938 - break;
3939 - }
3940 - case ClassComponent: {
3941 - const instance = fiber.stateNode;
3942 - if (typeof instance.componentWillUnmount === 'function') {
3943 - safelyCallComponentWillUnmount(fiber, fiber.return, instance);
3944 - }
3945 - break;
3946 - }
3947 - }
3948 - }
3949 -}
3950 -
3951 -function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3952 - if (__DEV__ && enableStrictEffects) {
3953 - // We don't need to re-check StrictEffectsMode here.
3954 - // This function is only called if that check has already passed.
3955 - switch (fiber.tag) {
3956 - case FunctionComponent:
3957 - case ForwardRef:
3958 - case SimpleMemoComponent: {
3959 - try {
3960 - commitHookEffectListUnmount(
3961 - HookPassive | HookHasEffect,
3962 - fiber,
3963 - fiber.return,
3964 - );
3965 - } catch (error) {
3966 - captureCommitPhaseError(fiber, fiber.return, error);
3967 - }
3968 - }
3969 - }
3970 - }
3971 -}
3972 -
3973 -export {
3974 - commitPlacement,
3975 - commitAttachRef,
3976 - commitDetachRef,
3977 - invokeLayoutEffectMountInDEV,
3978 - invokeLayoutEffectUnmountInDEV,
3979 - invokePassiveEffectMountInDEV,
3980 - invokePassiveEffectUnmountInDEV,
3981 -};
3840 +export {commitPlacement, commitAttachRef, commitDetachRef};
packages/react-reconciler/src/ReactFiberFlags.js
+28 -27
@@ -12,52 +12,53 @@ import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
12 export type Flags = number;
13
14 // Don't change these two values. They're used by React Dev Tools.
15 -export const NoFlags = /* */ 0b0000000000000000000000000;
16 -export const PerformedWork = /* */ 0b0000000000000000000000001;
15 +export const NoFlags = /* */ 0b00000000000000000000000000;
16 +export const PerformedWork = /* */ 0b00000000000000000000000001;
17
18 // You can change the rest (and add more).
19 -export const Placement = /* */ 0b0000000000000000000000010;
20 -export const Update = /* */ 0b0000000000000000000000100;
21 -export const ChildDeletion = /* */ 0b0000000000000000000001000;
22 -export const ContentReset = /* */ 0b0000000000000000000010000;
23 -export const Callback = /* */ 0b0000000000000000000100000;
24 -export const DidCapture = /* */ 0b0000000000000000001000000;
25 -export const ForceClientRender = /* */ 0b0000000000000000010000000;
26 -export const Ref = /* */ 0b0000000000000000100000000;
27 -export const Snapshot = /* */ 0b0000000000000001000000000;
28 -export const Passive = /* */ 0b0000000000000010000000000;
29 -export const Hydrating = /* */ 0b0000000000000100000000000;
30 -export const Visibility = /* */ 0b0000000000001000000000000;
31 -export const StoreConsistency = /* */ 0b0000000000010000000000000;
19 +export const Placement = /* */ 0b00000000000000000000000010;
20 +export const Update = /* */ 0b00000000000000000000000100;
21 +export const ChildDeletion = /* */ 0b00000000000000000000001000;
22 +export const ContentReset = /* */ 0b00000000000000000000010000;
23 +export const Callback = /* */ 0b00000000000000000000100000;
24 +export const DidCapture = /* */ 0b00000000000000000001000000;
25 +export const ForceClientRender = /* */ 0b00000000000000000010000000;
26 +export const Ref = /* */ 0b00000000000000000100000000;
27 +export const Snapshot = /* */ 0b00000000000000001000000000;
28 +export const Passive = /* */ 0b00000000000000010000000000;
29 +export const Hydrating = /* */ 0b00000000000000100000000000;
30 +export const Visibility = /* */ 0b00000000000001000000000000;
31 +export const StoreConsistency = /* */ 0b00000000000010000000000000;
32
33 export const LifecycleEffectMask =
34 Passive | Update | Callback | Ref | Snapshot | StoreConsistency;
35
36 // Union of all commit flags (flags with the lifetime of a particular commit)
37 -export const HostEffectMask = /* */ 0b0000000000011111111111111;
37 +export const HostEffectMask = /* */ 0b00000000000011111111111111;
38
39 // These are not really side effects, but we still reuse this field.
40 -export const Incomplete = /* */ 0b0000000000100000000000000;
41 -export const ShouldCapture = /* */ 0b0000000001000000000000000;
42 -export const ForceUpdateForLegacySuspense = /* */ 0b0000000010000000000000000;
43 -export const DidPropagateContext = /* */ 0b0000000100000000000000000;
44 -export const NeedsPropagation = /* */ 0b0000001000000000000000000;
45 -export const Forked = /* */ 0b0000010000000000000000000;
40 +export const Incomplete = /* */ 0b00000000000100000000000000;
41 +export const ShouldCapture = /* */ 0b00000000001000000000000000;
42 +export const ForceUpdateForLegacySuspense = /* */ 0b00000000010000000000000000;
43 +export const DidPropagateContext = /* */ 0b00000000100000000000000000;
44 +export const NeedsPropagation = /* */ 0b00000001000000000000000000;
45 +export const Forked = /* */ 0b00000010000000000000000000;
46
47 // Static tags describe aspects of a fiber that are not specific to a render,
48 // e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
49 // This enables us to defer more work in the unmount case,
50 // since we can defer traversing the tree during layout to look for Passive effects,
51 // and instead rely on the static flag as a signal that there may be cleanup work.
52 -export const RefStatic = /* */ 0b0000100000000000000000000;
53 -export const LayoutStatic = /* */ 0b0001000000000000000000000;
54 -export const PassiveStatic = /* */ 0b0010000000000000000000000;
52 +export const RefStatic = /* */ 0b00000100000000000000000000;
53 +export const LayoutStatic = /* */ 0b00001000000000000000000000;
54 +export const PassiveStatic = /* */ 0b00010000000000000000000000;
55
56 // These flags allow us to traverse to fibers that have effects on mount
57 // without traversing the entire tree after every commit for
58 // double invoking
59 -export const MountLayoutDev = /* */ 0b0100000000000000000000000;
60 -export const MountPassiveDev = /* */ 0b1000000000000000000000000;
59 +export const MountLayoutDev = /* */ 0b00100000000000000000000000;
60 +export const MountPassiveDev = /* */ 0b01000000000000000000000000;
61 +export const PlacementDEV = /* */ 0b10000000000000000000000000;
62
63 // Groups of flags that are used in the commit phase to skip over trees that
64 // don't contain effects, by checking subtreeFlags.
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+58 -59
@@ -7,11 +7,12 @@
7 * @flow
8 */
9
10 +import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11 +
12 import type {Wakeable} from 'shared/ReactTypes';
13 import type {Fiber, FiberRoot} from './ReactInternalTypes';
14 import type {Lanes, Lane} from './ReactFiberLane.new';
15 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14 -import type {Flags} from './ReactFiberFlags';
16 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
17 import type {EventPriority} from './ReactEventPriorities.new';
18 import type {
@@ -90,7 +91,13 @@ import {
91 } from './ReactFiber.new';
92 import {isRootDehydrated} from './ReactFiberShellHydration';
93 import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.new';
93 -import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
94 +import {
95 + NoMode,
96 + ProfileMode,
97 + ConcurrentMode,
98 + StrictLegacyMode,
99 + StrictEffectsMode,
100 +} from './ReactTypeOfMode';
101 import {
102 HostRoot,
103 IndeterminateComponent,
@@ -104,7 +111,7 @@ import {
111 SimpleMemoComponent,
112 Profiler,
113 } from './ReactWorkTags';
107 -import {LegacyRoot} from './ReactRootTags';
114 +import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
115 import {
116 NoFlags,
117 Incomplete,
@@ -115,8 +122,7 @@ import {
122 MutationMask,
123 LayoutMask,
124 PassiveMask,
118 - MountPassiveDev,
119 - MountLayoutDev,
125 + PlacementDEV,
126 } from './ReactFiberFlags';
127 import {
128 NoLanes,
@@ -176,10 +182,10 @@ import {
182 commitPassiveEffectDurations,
183 commitPassiveMountEffects,
184 commitPassiveUnmountEffects,
179 - invokeLayoutEffectMountInDEV,
180 - invokePassiveEffectMountInDEV,
181 - invokeLayoutEffectUnmountInDEV,
182 - invokePassiveEffectUnmountInDEV,
185 + disappearLayoutEffects,
186 + reconnectPassiveEffects,
187 + reappearLayoutEffects,
188 + disconnectPassiveEffect,
189 reportUncaughtErrorInDEV,
190 } from './ReactFiberCommitWork.new';
191 import {enqueueUpdate} from './ReactFiberClassUpdateQueue.new';
@@ -267,7 +273,7 @@ type ExecutionContext = number;
273 export const NoContext = /* */ 0b000;
274 const BatchedContext = /* */ 0b001;
275 const RenderContext = /* */ 0b010;
270 -const CommitContext = /* */ 0b100;
276 +export const CommitContext = /* */ 0b100;
277
278 type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
279 const RootInProgress = 0;
@@ -2422,7 +2428,7 @@ function commitRootImpl(
2428
2429 if (__DEV__ && enableStrictEffects) {
2430 if (!rootDidHavePassiveEffects) {
2425 - commitDoubleInvokeEffectsInDEV(root.current, false);
2431 + commitDoubleInvokeEffectsInDEV(root);
2432 }
2433 }
2434
@@ -2639,7 +2645,7 @@ function flushPassiveEffectsImpl() {
2645 }
2646
2647 if (__DEV__ && enableStrictEffects) {
2642 - commitDoubleInvokeEffectsInDEV(root.current, true);
2648 + commitDoubleInvokeEffectsInDEV(root);
2649 }
2650
2651 executionContext = prevExecutionContext;
@@ -2993,64 +2999,57 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2999 }
3000 }
3001
2996 -function commitDoubleInvokeEffectsInDEV(
2997 - fiber: Fiber,
2998 - hasPassiveEffects: boolean,
3002 +function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3003 + root: FiberRoot,
3004 + parentFiber: Fiber,
3005 + isInStrictMode: boolean,
3006 ) {
3000 - if (__DEV__ && enableStrictEffects) {
3001 - // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3002 - // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3003 - // Maybe not a big deal since this is DEV only behavior.
3007 + let child = parentFiber.child;
3008 + while (child !== null) {
3009 + doubleInvokeEffectsInDEV(root, child, isInStrictMode);
3010 + child = child.sibling;
3011 + }
3012 +}
3013
3014 +function doubleInvokeEffectsInDEV(
3015 + root: FiberRoot,
3016 + fiber: Fiber,
3017 + parentIsInStrictMode: boolean,
3018 +) {
3019 + const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
3020 + const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3021 + if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
3022 setCurrentDebugFiberInDEV(fiber);
3006 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3007 - if (hasPassiveEffects) {
3008 - invokeEffectsInDev(
3009 - fiber,
3010 - MountPassiveDev,
3011 - invokePassiveEffectUnmountInDEV,
3012 - );
3013 - }
3014 -
3015 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3016 - if (hasPassiveEffects) {
3017 - invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3023 + if (isInStrictMode) {
3024 + disappearLayoutEffects(fiber);
3025 + disconnectPassiveEffect(fiber);
3026 + reappearLayoutEffects(root, fiber.alternate, fiber, false);
3027 + reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3028 }
3029 resetCurrentDebugFiberInDEV();
3030 + } else {
3031 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
3032 }
3033 }
3034
3023 -function invokeEffectsInDev(
3024 - firstChild: Fiber,
3025 - fiberFlags: Flags,
3026 - invokeEffectFn: (fiber: Fiber) => void,
3027 -): void {
3035 +function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3036 if (__DEV__ && enableStrictEffects) {
3029 - // We don't need to re-check StrictEffectsMode here.
3030 - // This function is only called if that check has already passed.
3037 + let doubleInvokeEffects = true;
3038
3032 - let current = firstChild;
3033 - let subtreeRoot = null;
3034 - while (current !== null) {
3035 - const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3036 - if (
3037 - current !== subtreeRoot &&
3038 - current.child !== null &&
3039 - primarySubtreeFlag !== NoFlags
3040 - ) {
3041 - current = current.child;
3042 - } else {
3043 - if ((current.flags & fiberFlags) !== NoFlags) {
3044 - invokeEffectFn(current);
3045 - }
3046 -
3047 - if (current.sibling !== null) {
3048 - current = current.sibling;
3049 - } else {
3050 - current = subtreeRoot = current.return;
3051 - }
3052 - }
3039 + if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3040 + doubleInvokeEffects = false;
3041 + }
3042 + if (
3043 + root.tag === ConcurrentRoot &&
3044 + !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3045 + ) {
3046 + doubleInvokeEffects = false;
3047 }
3048 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3049 + root,
3050 + root.current,
3051 + doubleInvokeEffects,
3052 + );
3053 }
3054 }
3055
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+58 -59
@@ -7,11 +7,12 @@
7 * @flow
8 */
9
10 +import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11 +
12 import type {Wakeable} from 'shared/ReactTypes';
13 import type {Fiber, FiberRoot} from './ReactInternalTypes';
14 import type {Lanes, Lane} from './ReactFiberLane.old';
15 import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
14 -import type {Flags} from './ReactFiberFlags';
16 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
17 import type {EventPriority} from './ReactEventPriorities.old';
18 import type {
@@ -90,7 +91,13 @@ import {
91 } from './ReactFiber.old';
92 import {isRootDehydrated} from './ReactFiberShellHydration';
93 import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.old';
93 -import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
94 +import {
95 + NoMode,
96 + ProfileMode,
97 + ConcurrentMode,
98 + StrictLegacyMode,
99 + StrictEffectsMode,
100 +} from './ReactTypeOfMode';
101 import {
102 HostRoot,
103 IndeterminateComponent,
@@ -104,7 +111,7 @@ import {
111 SimpleMemoComponent,
112 Profiler,
113 } from './ReactWorkTags';
107 -import {LegacyRoot} from './ReactRootTags';
114 +import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
115 import {
116 NoFlags,
117 Incomplete,
@@ -115,8 +122,7 @@ import {
122 MutationMask,
123 LayoutMask,
124 PassiveMask,
118 - MountPassiveDev,
119 - MountLayoutDev,
125 + PlacementDEV,
126 } from './ReactFiberFlags';
127 import {
128 NoLanes,
@@ -176,10 +182,10 @@ import {
182 commitPassiveEffectDurations,
183 commitPassiveMountEffects,
184 commitPassiveUnmountEffects,
179 - invokeLayoutEffectMountInDEV,
180 - invokePassiveEffectMountInDEV,
181 - invokeLayoutEffectUnmountInDEV,
182 - invokePassiveEffectUnmountInDEV,
185 + disappearLayoutEffects,
186 + reconnectPassiveEffects,
187 + reappearLayoutEffects,
188 + disconnectPassiveEffect,
189 reportUncaughtErrorInDEV,
190 } from './ReactFiberCommitWork.old';
191 import {enqueueUpdate} from './ReactFiberClassUpdateQueue.old';
@@ -267,7 +273,7 @@ type ExecutionContext = number;
273 export const NoContext = /* */ 0b000;
274 const BatchedContext = /* */ 0b001;
275 const RenderContext = /* */ 0b010;
270 -const CommitContext = /* */ 0b100;
276 +export const CommitContext = /* */ 0b100;
277
278 type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
279 const RootInProgress = 0;
@@ -2422,7 +2428,7 @@ function commitRootImpl(
2428
2429 if (__DEV__ && enableStrictEffects) {
2430 if (!rootDidHavePassiveEffects) {
2425 - commitDoubleInvokeEffectsInDEV(root.current, false);
2431 + commitDoubleInvokeEffectsInDEV(root);
2432 }
2433 }
2434
@@ -2639,7 +2645,7 @@ function flushPassiveEffectsImpl() {
2645 }
2646
2647 if (__DEV__ && enableStrictEffects) {
2642 - commitDoubleInvokeEffectsInDEV(root.current, true);
2648 + commitDoubleInvokeEffectsInDEV(root);
2649 }
2650
2651 executionContext = prevExecutionContext;
@@ -2993,64 +2999,57 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2999 }
3000 }
3001
2996 -function commitDoubleInvokeEffectsInDEV(
2997 - fiber: Fiber,
2998 - hasPassiveEffects: boolean,
3002 +function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3003 + root: FiberRoot,
3004 + parentFiber: Fiber,
3005 + isInStrictMode: boolean,
3006 ) {
3000 - if (__DEV__ && enableStrictEffects) {
3001 - // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3002 - // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3003 - // Maybe not a big deal since this is DEV only behavior.
3007 + let child = parentFiber.child;
3008 + while (child !== null) {
3009 + doubleInvokeEffectsInDEV(root, child, isInStrictMode);
3010 + child = child.sibling;
3011 + }
3012 +}
3013
3014 +function doubleInvokeEffectsInDEV(
3015 + root: FiberRoot,
3016 + fiber: Fiber,
3017 + parentIsInStrictMode: boolean,
3018 +) {
3019 + const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
3020 + const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3021 + if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
3022 setCurrentDebugFiberInDEV(fiber);
3006 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3007 - if (hasPassiveEffects) {
3008 - invokeEffectsInDev(
3009 - fiber,
3010 - MountPassiveDev,
3011 - invokePassiveEffectUnmountInDEV,
3012 - );
3013 - }
3014 -
3015 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3016 - if (hasPassiveEffects) {
3017 - invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3023 + if (isInStrictMode) {
3024 + disappearLayoutEffects(fiber);
3025 + disconnectPassiveEffect(fiber);
3026 + reappearLayoutEffects(root, fiber.alternate, fiber, false);
3027 + reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3028 }
3029 resetCurrentDebugFiberInDEV();
3030 + } else {
3031 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
3032 }
3033 }
3034
3023 -function invokeEffectsInDev(
3024 - firstChild: Fiber,
3025 - fiberFlags: Flags,
3026 - invokeEffectFn: (fiber: Fiber) => void,
3027 -): void {
3035 +function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3036 if (__DEV__ && enableStrictEffects) {
3029 - // We don't need to re-check StrictEffectsMode here.
3030 - // This function is only called if that check has already passed.
3037 + let doubleInvokeEffects = true;
3038
3032 - let current = firstChild;
3033 - let subtreeRoot = null;
3034 - while (current !== null) {
3035 - const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3036 - if (
3037 - current !== subtreeRoot &&
3038 - current.child !== null &&
3039 - primarySubtreeFlag !== NoFlags
3040 - ) {
3041 - current = current.child;
3042 - } else {
3043 - if ((current.flags & fiberFlags) !== NoFlags) {
3044 - invokeEffectFn(current);
3045 - }
3046 -
3047 - if (current.sibling !== null) {
3048 - current = current.sibling;
3049 - } else {
3050 - current = subtreeRoot = current.return;
3051 - }
3052 - }
3039 + if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3040 + doubleInvokeEffects = false;
3041 + }
3042 + if (
3043 + root.tag === ConcurrentRoot &&
3044 + !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3045 + ) {
3046 + doubleInvokeEffects = false;
3047 }
3048 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3049 + root,
3050 + root.current,
3051 + doubleInvokeEffects,
3052 + );
3053 }
3054 }
3055
packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js new
+95
@@ -0,0 +1,95 @@
1 +let React;
2 +let Offscreen;
3 +let ReactNoop;
4 +let act;
5 +let log;
6 +
7 +describe('ReactOffscreenStrictMode', () => {
8 + beforeEach(() => {
9 + jest.resetModules();
10 + log = [];
11 +
12 + React = require('react');
13 + Offscreen = React.unstable_Offscreen;
14 + ReactNoop = require('react-noop-renderer');
15 + act = require('jest-react').act;
16 + });
17 +
18 + function Component({label}) {
19 + React.useEffect(() => {
20 + log.push(`${label}: useEffect mount`);
21 + return () => log.push(`${label}: useEffect unmount`);
22 + });
23 +
24 + React.useLayoutEffect(() => {
25 + log.push(`${label}: useLayoutEffect mount`);
26 + return () => log.push(`${label}: useLayoutEffect unmount`);
27 + });
28 +
29 + log.push(`${label}: render`);
30 +
31 + return <span>label</span>;
32 + }
33 +
34 + // @gate __DEV__ && enableStrictEffects && enableOffscreen
35 + it('should trigger strict effects when offscreen is visible', () => {
36 + act(() => {
37 + ReactNoop.render(
38 + <React.StrictMode>
39 + <Offscreen mode="visible">
40 + <Component label="A" />
41 + </Offscreen>
42 + </React.StrictMode>,
43 + );
44 + });
45 +
46 + expect(log).toEqual([
47 + 'A: render',
48 + 'A: render',
49 + 'A: useLayoutEffect mount',
50 + 'A: useEffect mount',
51 + 'A: useLayoutEffect unmount',
52 + 'A: useEffect unmount',
53 + 'A: useLayoutEffect mount',
54 + 'A: useEffect mount',
55 + ]);
56 + });
57 +
58 + // @gate __DEV__ && enableStrictEffects && enableOffscreen
59 + it('should not trigger strict effects when offscreen is hidden', () => {
60 + act(() => {
61 + ReactNoop.render(
62 + <React.StrictMode>
63 + <Offscreen mode="hidden">
64 + <Component label="A" />
65 + </Offscreen>
66 + </React.StrictMode>,
67 + );
68 + });
69 +
70 + expect(log).toEqual(['A: render', 'A: render']);
71 +
72 + log = [];
73 +
74 + act(() => {
75 + ReactNoop.render(
76 + <React.StrictMode>
77 + <Offscreen mode="visible">
78 + <Component label="A" />
79 + </Offscreen>
80 + </React.StrictMode>,
81 + );
82 + });
83 +
84 + expect(log).toEqual([
85 + 'A: render',
86 + 'A: render',
87 + 'A: useLayoutEffect mount',
88 + 'A: useEffect mount',
89 + 'A: useLayoutEffect unmount',
90 + 'A: useEffect unmount',
91 + 'A: useLayoutEffect mount',
92 + 'A: useEffect mount',
93 + ]);
94 + });
95 +});
packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
+24 -3
@@ -153,10 +153,8 @@ describe('StrictEffectsMode defaults', () => {
153 </>,
154 );
155
156 - expect(Scheduler).toFlushAndYieldThrough([
157 - 'useLayoutEffect mount "one"',
158 - ]);
156 expect(Scheduler).toFlushAndYield([
157 + 'useLayoutEffect mount "one"',
158 'useEffect mount "one"',
159 'useLayoutEffect unmount "one"',
160 'useEffect unmount "one"',
@@ -381,6 +379,29 @@ describe('StrictEffectsMode defaults', () => {
379 expect(Scheduler).toHaveYielded([]);
380 });
381
382 + it('disconnects refs during double invoking', () => {
383 + const onRefMock = jest.fn();
384 + function App({text}) {
385 + return (
386 + <span
387 + ref={ref => {
388 + onRefMock(ref);
389 + }}>
390 + text
391 + </span>
392 + );
393 + }
394 +
395 + act(() => {
396 + ReactNoop.render(<App text={'mount'} />);
397 + });
398 +
399 + expect(onRefMock.mock.calls.length).toBe(3);
400 + expect(onRefMock.mock.calls[0][0]).not.toBeNull();
401 + expect(onRefMock.mock.calls[1][0]).toBe(null);
402 + expect(onRefMock.mock.calls[2][0]).not.toBeNull();
403 + });
404 +
405 it('passes the right context to class component lifecycles', () => {
406 class App extends React.PureComponent {
407 test() {}
packages/react/src/__tests__/ReactStrictMode-test.js
+25 -49
@@ -67,6 +67,7 @@ describe('ReactStrictMode', () => {
67 );
68 });
69
70 + // @gate __DEV__ && !enableStrictEffects
71 it('should invoke precommit lifecycle methods twice', () => {
72 let log = [];
73 let shouldComponentUpdate = false;
@@ -107,24 +108,15 @@ describe('ReactStrictMode', () => {
108 container,
109 );
110
110 - if (__DEV__) {
111 - expect(log).toEqual([
112 - 'constructor',
113 - 'constructor',
114 - 'getDerivedStateFromProps',
115 - 'getDerivedStateFromProps',
116 - 'render',
117 - 'render',
118 - 'componentDidMount',
119 - ]);
120 - } else {
121 - expect(log).toEqual([
122 - 'constructor',
123 - 'getDerivedStateFromProps',
124 - 'render',
125 - 'componentDidMount',
126 - ]);
127 - }
111 + expect(log).toEqual([
112 + 'constructor',
113 + 'constructor',
114 + 'getDerivedStateFromProps',
115 + 'getDerivedStateFromProps',
116 + 'render',
117 + 'render',
118 + 'componentDidMount',
119 + ]);
120
121 log = [];
122 shouldComponentUpdate = true;
@@ -135,24 +127,15 @@ describe('ReactStrictMode', () => {
127 </React.StrictMode>,
128 container,
129 );
138 - if (__DEV__) {
139 - expect(log).toEqual([
140 - 'getDerivedStateFromProps',
141 - 'getDerivedStateFromProps',
142 - 'shouldComponentUpdate',
143 - 'shouldComponentUpdate',
144 - 'render',
145 - 'render',
146 - 'componentDidUpdate',
147 - ]);
148 - } else {
149 - expect(log).toEqual([
150 - 'getDerivedStateFromProps',
151 - 'shouldComponentUpdate',
152 - 'render',
153 - 'componentDidUpdate',
154 - ]);
155 - }
130 + expect(log).toEqual([
131 + 'getDerivedStateFromProps',
132 + 'getDerivedStateFromProps',
133 + 'shouldComponentUpdate',
134 + 'shouldComponentUpdate',
135 + 'render',
136 + 'render',
137 + 'componentDidUpdate',
138 + ]);
139
140 log = [];
141 shouldComponentUpdate = false;
@@ -164,19 +147,12 @@ describe('ReactStrictMode', () => {
147 container,
148 );
149
167 - if (__DEV__) {
168 - expect(log).toEqual([
169 - 'getDerivedStateFromProps',
170 - 'getDerivedStateFromProps',
171 - 'shouldComponentUpdate',
172 - 'shouldComponentUpdate',
173 - ]);
174 - } else {
175 - expect(log).toEqual([
176 - 'getDerivedStateFromProps',
177 - 'shouldComponentUpdate',
178 - ]);
179 - }
150 + expect(log).toEqual([
151 + 'getDerivedStateFromProps',
152 + 'getDerivedStateFromProps',
153 + 'shouldComponentUpdate',
154 + 'shouldComponentUpdate',
155 + ]);
156 });
157
158 it('should invoke setState callbacks twice', () => {