@samitouri / QOS-React-2 / commits / abbbdf4cec

Put modern StrictMode behind a feature flag (#25365)

* Put modern StrictMode behind a feature flag * Remove unneeded flag

Samuel Susla committed Sep 30, 2022 at 17:06 UTC abbbdf4cec992666885cc67344563795a333d4a2
21 files changed +637 -90
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+45 -6
@@ -13,7 +13,12 @@ import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
13 import type {Flags} from './ReactFiberFlags';
14
15 import * as React from 'react';
16 -import {LayoutStatic, Update, Snapshot} from './ReactFiberFlags';
16 +import {
17 + LayoutStatic,
18 + Update,
19 + Snapshot,
20 + MountLayoutDev,
21 +} from './ReactFiberFlags';
22 import {
23 debugRenderPhaseSideEffectsForStrictMode,
24 disableLegacyContext,
@@ -21,6 +26,7 @@ import {
26 enableSchedulingProfiler,
27 warnAboutDeprecatedLifecycles,
28 enableLazyContextPropagation,
29 + enableStrictEffects,
30 } from 'shared/ReactFeatureFlags';
31 import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
32 import {isMounted} from './ReactFiberTreeReflection';
@@ -33,7 +39,12 @@ import isArray from 'shared/isArray';
39 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
40
41 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
36 -import {DebugTracingMode, StrictLegacyMode} from './ReactTypeOfMode';
42 +import {
43 + DebugTracingMode,
44 + NoMode,
45 + StrictLegacyMode,
46 + StrictEffectsMode,
47 +} from './ReactTypeOfMode';
48
49 import {
50 enqueueUpdate,
@@ -896,7 +907,14 @@ function mountClassInstance(
907 }
908
909 if (typeof instance.componentDidMount === 'function') {
899 - const fiberFlags: Flags = Update | LayoutStatic;
910 + let fiberFlags: Flags = Update | LayoutStatic;
911 + if (
912 + __DEV__ &&
913 + enableStrictEffects &&
914 + (workInProgress.mode & StrictEffectsMode) !== NoMode
915 + ) {
916 + fiberFlags |= MountLayoutDev;
917 + }
918 workInProgress.flags |= fiberFlags;
919 }
920 }
@@ -967,7 +985,14 @@ function resumeMountClassInstance(
985 // If an update was already in progress, we should schedule an Update
986 // effect even though we're bailing out, so that cWU/cDU are called.
987 if (typeof instance.componentDidMount === 'function') {
970 - const fiberFlags: Flags = Update | LayoutStatic;
988 + let fiberFlags: Flags = Update | LayoutStatic;
989 + if (
990 + __DEV__ &&
991 + enableStrictEffects &&
992 + (workInProgress.mode & StrictEffectsMode) !== NoMode
993 + ) {
994 + fiberFlags |= MountLayoutDev;
995 + }
996 workInProgress.flags |= fiberFlags;
997 }
998 return false;
@@ -1011,14 +1036,28 @@ function resumeMountClassInstance(
1036 }
1037 }
1038 if (typeof instance.componentDidMount === 'function') {
1014 - const fiberFlags: Flags = Update | LayoutStatic;
1039 + let fiberFlags: Flags = Update | LayoutStatic;
1040 + if (
1041 + __DEV__ &&
1042 + enableStrictEffects &&
1043 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1044 + ) {
1045 + fiberFlags |= MountLayoutDev;
1046 + }
1047 workInProgress.flags |= fiberFlags;
1048 }
1049 } else {
1050 // If an update was already in progress, we should schedule an Update
1051 // effect even though we're bailing out, so that cWU/cDU are called.
1052 if (typeof instance.componentDidMount === 'function') {
1021 - const fiberFlags: Flags = Update | LayoutStatic;
1053 + let fiberFlags: Flags = Update | LayoutStatic;
1054 + if (
1055 + __DEV__ &&
1056 + enableStrictEffects &&
1057 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1058 + ) {
1059 + fiberFlags |= MountLayoutDev;
1060 + }
1061 workInProgress.flags |= fiberFlags;
1062 }
1063
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+45 -6
@@ -13,7 +13,12 @@ import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
13 import type {Flags} from './ReactFiberFlags';
14
15 import * as React from 'react';
16 -import {LayoutStatic, Update, Snapshot} from './ReactFiberFlags';
16 +import {
17 + LayoutStatic,
18 + Update,
19 + Snapshot,
20 + MountLayoutDev,
21 +} from './ReactFiberFlags';
22 import {
23 debugRenderPhaseSideEffectsForStrictMode,
24 disableLegacyContext,
@@ -21,6 +26,7 @@ import {
26 enableSchedulingProfiler,
27 warnAboutDeprecatedLifecycles,
28 enableLazyContextPropagation,
29 + enableStrictEffects,
30 } from 'shared/ReactFeatureFlags';
31 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
32 import {isMounted} from './ReactFiberTreeReflection';
@@ -33,7 +39,12 @@ import isArray from 'shared/isArray';
39 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
40
41 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
36 -import {DebugTracingMode, StrictLegacyMode} from './ReactTypeOfMode';
42 +import {
43 + DebugTracingMode,
44 + NoMode,
45 + StrictLegacyMode,
46 + StrictEffectsMode,
47 +} from './ReactTypeOfMode';
48
49 import {
50 enqueueUpdate,
@@ -896,7 +907,14 @@ function mountClassInstance(
907 }
908
909 if (typeof instance.componentDidMount === 'function') {
899 - const fiberFlags: Flags = Update | LayoutStatic;
910 + let fiberFlags: Flags = Update | LayoutStatic;
911 + if (
912 + __DEV__ &&
913 + enableStrictEffects &&
914 + (workInProgress.mode & StrictEffectsMode) !== NoMode
915 + ) {
916 + fiberFlags |= MountLayoutDev;
917 + }
918 workInProgress.flags |= fiberFlags;
919 }
920 }
@@ -967,7 +985,14 @@ function resumeMountClassInstance(
985 // If an update was already in progress, we should schedule an Update
986 // effect even though we're bailing out, so that cWU/cDU are called.
987 if (typeof instance.componentDidMount === 'function') {
970 - const fiberFlags: Flags = Update | LayoutStatic;
988 + let fiberFlags: Flags = Update | LayoutStatic;
989 + if (
990 + __DEV__ &&
991 + enableStrictEffects &&
992 + (workInProgress.mode & StrictEffectsMode) !== NoMode
993 + ) {
994 + fiberFlags |= MountLayoutDev;
995 + }
996 workInProgress.flags |= fiberFlags;
997 }
998 return false;
@@ -1011,14 +1036,28 @@ function resumeMountClassInstance(
1036 }
1037 }
1038 if (typeof instance.componentDidMount === 'function') {
1014 - const fiberFlags: Flags = Update | LayoutStatic;
1039 + let fiberFlags: Flags = Update | LayoutStatic;
1040 + if (
1041 + __DEV__ &&
1042 + enableStrictEffects &&
1043 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1044 + ) {
1045 + fiberFlags |= MountLayoutDev;
1046 + }
1047 workInProgress.flags |= fiberFlags;
1048 }
1049 } else {
1050 // If an update was already in progress, we should schedule an Update
1051 // effect even though we're bailing out, so that cWU/cDU are called.
1052 if (typeof instance.componentDidMount === 'function') {
1021 - const fiberFlags: Flags = Update | LayoutStatic;
1053 + let fiberFlags: Flags = Update | LayoutStatic;
1054 + if (
1055 + __DEV__ &&
1056 + enableStrictEffects &&
1057 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1058 + ) {
1059 + fiberFlags |= MountLayoutDev;
1060 + }
1061 workInProgress.flags |= fiberFlags;
1062 }
1063
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+109 -1
@@ -50,6 +50,7 @@ import {
50 enableCache,
51 enableTransitionTracing,
52 enableUseEventHook,
53 + enableStrictEffects,
54 } from 'shared/ReactFeatureFlags';
55 import {
56 FunctionComponent,
@@ -4109,4 +4110,111 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
4110 }
4111 }
4112
4112 -export {commitPlacement, commitAttachRef, commitDetachRef};
4113 +function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
4114 + if (__DEV__ && enableStrictEffects) {
4115 + // We don't need to re-check StrictEffectsMode here.
4116 + // This function is only called if that check has already passed.
4117 + switch (fiber.tag) {
4118 + case FunctionComponent:
4119 + case ForwardRef:
4120 + case SimpleMemoComponent: {
4121 + try {
4122 + commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
4123 + } catch (error) {
4124 + captureCommitPhaseError(fiber, fiber.return, error);
4125 + }
4126 + break;
4127 + }
4128 + case ClassComponent: {
4129 + const instance = fiber.stateNode;
4130 + try {
4131 + instance.componentDidMount();
4132 + } catch (error) {
4133 + captureCommitPhaseError(fiber, fiber.return, error);
4134 + }
4135 + break;
4136 + }
4137 + }
4138 + }
4139 +}
4140 +
4141 +function invokePassiveEffectMountInDEV(fiber: Fiber): void {
4142 + if (__DEV__ && enableStrictEffects) {
4143 + // We don't need to re-check StrictEffectsMode here.
4144 + // This function is only called if that check has already passed.
4145 + switch (fiber.tag) {
4146 + case FunctionComponent:
4147 + case ForwardRef:
4148 + case SimpleMemoComponent: {
4149 + try {
4150 + commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
4151 + } catch (error) {
4152 + captureCommitPhaseError(fiber, fiber.return, error);
4153 + }
4154 + break;
4155 + }
4156 + }
4157 + }
4158 +}
4159 +
4160 +function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
4161 + if (__DEV__ && enableStrictEffects) {
4162 + // We don't need to re-check StrictEffectsMode here.
4163 + // This function is only called if that check has already passed.
4164 + switch (fiber.tag) {
4165 + case FunctionComponent:
4166 + case ForwardRef:
4167 + case SimpleMemoComponent: {
4168 + try {
4169 + commitHookEffectListUnmount(
4170 + HookLayout | HookHasEffect,
4171 + fiber,
4172 + fiber.return,
4173 + );
4174 + } catch (error) {
4175 + captureCommitPhaseError(fiber, fiber.return, error);
4176 + }
4177 + break;
4178 + }
4179 + case ClassComponent: {
4180 + const instance = fiber.stateNode;
4181 + if (typeof instance.componentWillUnmount === 'function') {
4182 + safelyCallComponentWillUnmount(fiber, fiber.return, instance);
4183 + }
4184 + break;
4185 + }
4186 + }
4187 + }
4188 +}
4189 +
4190 +function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
4191 + if (__DEV__ && enableStrictEffects) {
4192 + // We don't need to re-check StrictEffectsMode here.
4193 + // This function is only called if that check has already passed.
4194 + switch (fiber.tag) {
4195 + case FunctionComponent:
4196 + case ForwardRef:
4197 + case SimpleMemoComponent: {
4198 + try {
4199 + commitHookEffectListUnmount(
4200 + HookPassive | HookHasEffect,
4201 + fiber,
4202 + fiber.return,
4203 + );
4204 + } catch (error) {
4205 + captureCommitPhaseError(fiber, fiber.return, error);
4206 + }
4207 + }
4208 + }
4209 + }
4210 +}
4211 +
4212 +export {
4213 + commitPlacement,
4214 + commitAttachRef,
4215 + commitDetachRef,
4216 + invokeLayoutEffectMountInDEV,
4217 + invokeLayoutEffectUnmountInDEV,
4218 + invokePassiveEffectMountInDEV,
4219 + invokePassiveEffectUnmountInDEV,
4220 +};
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+109 -1
@@ -50,6 +50,7 @@ import {
50 enableCache,
51 enableTransitionTracing,
52 enableUseEventHook,
53 + enableStrictEffects,
54 } from 'shared/ReactFeatureFlags';
55 import {
56 FunctionComponent,
@@ -4109,4 +4110,111 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
4110 }
4111 }
4112
4112 -export {commitPlacement, commitAttachRef, commitDetachRef};
4113 +function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
4114 + if (__DEV__ && enableStrictEffects) {
4115 + // We don't need to re-check StrictEffectsMode here.
4116 + // This function is only called if that check has already passed.
4117 + switch (fiber.tag) {
4118 + case FunctionComponent:
4119 + case ForwardRef:
4120 + case SimpleMemoComponent: {
4121 + try {
4122 + commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
4123 + } catch (error) {
4124 + captureCommitPhaseError(fiber, fiber.return, error);
4125 + }
4126 + break;
4127 + }
4128 + case ClassComponent: {
4129 + const instance = fiber.stateNode;
4130 + try {
4131 + instance.componentDidMount();
4132 + } catch (error) {
4133 + captureCommitPhaseError(fiber, fiber.return, error);
4134 + }
4135 + break;
4136 + }
4137 + }
4138 + }
4139 +}
4140 +
4141 +function invokePassiveEffectMountInDEV(fiber: Fiber): void {
4142 + if (__DEV__ && enableStrictEffects) {
4143 + // We don't need to re-check StrictEffectsMode here.
4144 + // This function is only called if that check has already passed.
4145 + switch (fiber.tag) {
4146 + case FunctionComponent:
4147 + case ForwardRef:
4148 + case SimpleMemoComponent: {
4149 + try {
4150 + commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
4151 + } catch (error) {
4152 + captureCommitPhaseError(fiber, fiber.return, error);
4153 + }
4154 + break;
4155 + }
4156 + }
4157 + }
4158 +}
4159 +
4160 +function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
4161 + if (__DEV__ && enableStrictEffects) {
4162 + // We don't need to re-check StrictEffectsMode here.
4163 + // This function is only called if that check has already passed.
4164 + switch (fiber.tag) {
4165 + case FunctionComponent:
4166 + case ForwardRef:
4167 + case SimpleMemoComponent: {
4168 + try {
4169 + commitHookEffectListUnmount(
4170 + HookLayout | HookHasEffect,
4171 + fiber,
4172 + fiber.return,
4173 + );
4174 + } catch (error) {
4175 + captureCommitPhaseError(fiber, fiber.return, error);
4176 + }
4177 + break;
4178 + }
4179 + case ClassComponent: {
4180 + const instance = fiber.stateNode;
4181 + if (typeof instance.componentWillUnmount === 'function') {
4182 + safelyCallComponentWillUnmount(fiber, fiber.return, instance);
4183 + }
4184 + break;
4185 + }
4186 + }
4187 + }
4188 +}
4189 +
4190 +function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
4191 + if (__DEV__ && enableStrictEffects) {
4192 + // We don't need to re-check StrictEffectsMode here.
4193 + // This function is only called if that check has already passed.
4194 + switch (fiber.tag) {
4195 + case FunctionComponent:
4196 + case ForwardRef:
4197 + case SimpleMemoComponent: {
4198 + try {
4199 + commitHookEffectListUnmount(
4200 + HookPassive | HookHasEffect,
4201 + fiber,
4202 + fiber.return,
4203 + );
4204 + } catch (error) {
4205 + captureCommitPhaseError(fiber, fiber.return, error);
4206 + }
4207 + }
4208 + }
4209 + }
4210 +}
4211 +
4212 +export {
4213 + commitPlacement,
4214 + commitAttachRef,
4215 + commitDetachRef,
4216 + invokeLayoutEffectMountInDEV,
4217 + invokeLayoutEffectUnmountInDEV,
4218 + invokePassiveEffectMountInDEV,
4219 + invokePassiveEffectUnmountInDEV,
4220 +};
packages/react-reconciler/src/ReactFiberFlags.js
+28 -26
@@ -12,49 +12,51 @@ 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 = /* */ 0b000000000000000000000000;
16 -export const PerformedWork = /* */ 0b000000000000000000000001;
15 +export const NoFlags = /* */ 0b00000000000000000000000000;
16 +export const PerformedWork = /* */ 0b00000000000000000000000001;
17
18 // You can change the rest (and add more).
19 -export const Placement = /* */ 0b000000000000000000000010;
20 -export const Update = /* */ 0b000000000000000000000100;
21 -export const ChildDeletion = /* */ 0b000000000000000000001000;
22 -export const ContentReset = /* */ 0b000000000000000000010000;
23 -export const Callback = /* */ 0b000000000000000000100000;
24 -export const DidCapture = /* */ 0b000000000000000001000000;
25 -export const ForceClientRender = /* */ 0b000000000000000010000000;
26 -export const Ref = /* */ 0b000000000000000100000000;
27 -export const Snapshot = /* */ 0b000000000000001000000000;
28 -export const Passive = /* */ 0b000000000000010000000000;
29 -export const Hydrating = /* */ 0b000000000000100000000000;
30 -export const Visibility = /* */ 0b000000000001000000000000;
31 -export const StoreConsistency = /* */ 0b000000000010000000000000;
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 = /* */ 0b000000000011111111111111;
37 +export const HostEffectMask = /* */ 0b00000000000011111111111111;
38
39 // These are not really side effects, but we still reuse this field.
40 -export const Incomplete = /* */ 0b000000000100000000000000;
41 -export const ShouldCapture = /* */ 0b000000001000000000000000;
42 -export const ForceUpdateForLegacySuspense = /* */ 0b000000010000000000000000;
43 -export const DidPropagateContext = /* */ 0b000000100000000000000000;
44 -export const NeedsPropagation = /* */ 0b000001000000000000000000;
45 -export const Forked = /* */ 0b000010000000000000000000;
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 = /* */ 0b000100000000000000000000;
53 -export const LayoutStatic = /* */ 0b001000000000000000000000;
54 -export const PassiveStatic = /* */ 0b010000000000000000000000;
52 +export const RefStatic = /* */ 0b00000100000000000000000000;
53 +export const LayoutStatic = /* */ 0b00001000000000000000000000;
54 +export const PassiveStatic = /* */ 0b00010000000000000000000000;
55
56 // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
57 -export const PlacementDEV = /* */ 0b100000000000000000000000;
57 +export const PlacementDEV = /* */ 0b00100000000000000000000000;
58 +export const MountLayoutDev = /* */ 0b01000000000000000000000000;
59 +export const MountPassiveDev = /* */ 0b10000000000000000000000000;
60
61 // Groups of flags that are used in the commit phase to skip over trees that
62 // don't contain effects, by checking subtreeFlags.
packages/react-reconciler/src/ReactFiberHooks.new.js
+60 -10
@@ -42,13 +42,19 @@ import {
42 enableUseHook,
43 enableUseMemoCacheHook,
44 enableUseEventHook,
45 + enableStrictEffects,
46 } from 'shared/ReactFeatureFlags';
47 import {
48 REACT_CONTEXT_TYPE,
49 REACT_SERVER_CONTEXT_TYPE,
50 } from 'shared/ReactSymbols';
51
51 -import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
52 +import {
53 + NoMode,
54 + ConcurrentMode,
55 + DebugTracingMode,
56 + StrictEffectsMode,
57 +} from './ReactTypeOfMode';
58 import {
59 NoLane,
60 SyncLane,
@@ -81,6 +87,8 @@ import {
87 StaticMask as StaticMaskEffect,
88 Update as UpdateEffect,
89 StoreConsistency,
90 + MountLayoutDev as MountLayoutDevEffect,
91 + MountPassiveDev as MountPassiveDevEffect,
92 } from './ReactFiberFlags';
93 import {
94 HasEffect as HookHasEffect,
@@ -590,7 +598,22 @@ export function bailoutHooks(
598 lanes: Lanes,
599 ) {
600 workInProgress.updateQueue = current.updateQueue;
593 - workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
601 + // TODO: Don't need to reset the flags here, because they're reset in the
602 + // complete phase (bubbleProperties).
603 + if (
604 + __DEV__ &&
605 + enableStrictEffects &&
606 + (workInProgress.mode & StrictEffectsMode) !== NoMode
607 + ) {
608 + workInProgress.flags &= ~(
609 + MountPassiveDevEffect |
610 + MountLayoutDevEffect |
611 + PassiveEffect |
612 + UpdateEffect
613 + );
614 + } else {
615 + workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
616 + }
617 current.lanes = removeLanes(current.lanes, lanes);
618 }
619
@@ -1859,12 +1882,25 @@ function mountEffect(
1882 create: () => (() => void) | void,
1883 deps: Array<mixed> | void | null,
1884 ): void {
1862 - return mountEffectImpl(
1863 - PassiveEffect | PassiveStaticEffect,
1864 - HookPassive,
1865 - create,
1866 - deps,
1867 - );
1885 + if (
1886 + __DEV__ &&
1887 + enableStrictEffects &&
1888 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1889 + ) {
1890 + return mountEffectImpl(
1891 + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1892 + HookPassive,
1893 + create,
1894 + deps,
1895 + );
1896 + } else {
1897 + return mountEffectImpl(
1898 + PassiveEffect | PassiveStaticEffect,
1899 + HookPassive,
1900 + create,
1901 + deps,
1902 + );
1903 + }
1904 }
1905
1906 function updateEffect(
@@ -1940,7 +1976,14 @@ function mountLayoutEffect(
1976 create: () => (() => void) | void,
1977 deps: Array<mixed> | void | null,
1978 ): void {
1943 - const fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
1979 + let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
1980 + if (
1981 + __DEV__ &&
1982 + enableStrictEffects &&
1983 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1984 + ) {
1985 + fiberFlags |= MountLayoutDevEffect;
1986 + }
1987 return mountEffectImpl(fiberFlags, HookLayout, create, deps);
1988 }
1989
@@ -2000,7 +2043,14 @@ function mountImperativeHandle<T>(
2043 const effectDeps =
2044 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
2045
2003 - const fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2046 + let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2047 + if (
2048 + __DEV__ &&
2049 + enableStrictEffects &&
2050 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2051 + ) {
2052 + fiberFlags |= MountLayoutDevEffect;
2053 + }
2054 return mountEffectImpl(
2055 fiberFlags,
2056 HookLayout,
packages/react-reconciler/src/ReactFiberHooks.old.js
+60 -10
@@ -42,13 +42,19 @@ import {
42 enableUseHook,
43 enableUseMemoCacheHook,
44 enableUseEventHook,
45 + enableStrictEffects,
46 } from 'shared/ReactFeatureFlags';
47 import {
48 REACT_CONTEXT_TYPE,
49 REACT_SERVER_CONTEXT_TYPE,
50 } from 'shared/ReactSymbols';
51
51 -import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
52 +import {
53 + NoMode,
54 + ConcurrentMode,
55 + DebugTracingMode,
56 + StrictEffectsMode,
57 +} from './ReactTypeOfMode';
58 import {
59 NoLane,
60 SyncLane,
@@ -81,6 +87,8 @@ import {
87 StaticMask as StaticMaskEffect,
88 Update as UpdateEffect,
89 StoreConsistency,
90 + MountLayoutDev as MountLayoutDevEffect,
91 + MountPassiveDev as MountPassiveDevEffect,
92 } from './ReactFiberFlags';
93 import {
94 HasEffect as HookHasEffect,
@@ -590,7 +598,22 @@ export function bailoutHooks(
598 lanes: Lanes,
599 ) {
600 workInProgress.updateQueue = current.updateQueue;
593 - workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
601 + // TODO: Don't need to reset the flags here, because they're reset in the
602 + // complete phase (bubbleProperties).
603 + if (
604 + __DEV__ &&
605 + enableStrictEffects &&
606 + (workInProgress.mode & StrictEffectsMode) !== NoMode
607 + ) {
608 + workInProgress.flags &= ~(
609 + MountPassiveDevEffect |
610 + MountLayoutDevEffect |
611 + PassiveEffect |
612 + UpdateEffect
613 + );
614 + } else {
615 + workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
616 + }
617 current.lanes = removeLanes(current.lanes, lanes);
618 }
619
@@ -1859,12 +1882,25 @@ function mountEffect(
1882 create: () => (() => void) | void,
1883 deps: Array<mixed> | void | null,
1884 ): void {
1862 - return mountEffectImpl(
1863 - PassiveEffect | PassiveStaticEffect,
1864 - HookPassive,
1865 - create,
1866 - deps,
1867 - );
1885 + if (
1886 + __DEV__ &&
1887 + enableStrictEffects &&
1888 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1889 + ) {
1890 + return mountEffectImpl(
1891 + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1892 + HookPassive,
1893 + create,
1894 + deps,
1895 + );
1896 + } else {
1897 + return mountEffectImpl(
1898 + PassiveEffect | PassiveStaticEffect,
1899 + HookPassive,
1900 + create,
1901 + deps,
1902 + );
1903 + }
1904 }
1905
1906 function updateEffect(
@@ -1940,7 +1976,14 @@ function mountLayoutEffect(
1976 create: () => (() => void) | void,
1977 deps: Array<mixed> | void | null,
1978 ): void {
1943 - const fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
1979 + let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
1980 + if (
1981 + __DEV__ &&
1982 + enableStrictEffects &&
1983 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1984 + ) {
1985 + fiberFlags |= MountLayoutDevEffect;
1986 + }
1987 return mountEffectImpl(fiberFlags, HookLayout, create, deps);
1988 }
1989
@@ -2000,7 +2043,14 @@ function mountImperativeHandle<T>(
2043 const effectDeps =
2044 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
2045
2003 - const fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2046 + let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2047 + if (
2048 + __DEV__ &&
2049 + enableStrictEffects &&
2050 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2051 + ) {
2052 + fiberFlags |= MountLayoutDevEffect;
2053 + }
2054 return mountEffectImpl(
2055 fiberFlags,
2056 HookLayout,
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+79 -14
@@ -40,6 +40,7 @@ import {
40 enableUpdaterTracking,
41 enableCache,
42 enableTransitionTracing,
43 + useModernStrictMode,
44 } from 'shared/ReactFeatureFlags';
45 import ReactSharedInternals from 'shared/ReactSharedInternals';
46 import is from 'shared/objectIs';
@@ -113,6 +114,7 @@ import {
114 Profiler,
115 } from './ReactWorkTags';
116 import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
117 +import type {Flags} from './ReactFiberFlags';
118 import {
119 NoFlags,
120 Incomplete,
@@ -125,6 +127,8 @@ import {
127 PassiveMask,
128 PlacementDEV,
129 Visibility,
130 + MountPassiveDev,
131 + MountLayoutDev,
132 } from './ReactFiberFlags';
133 import {
134 NoLanes,
@@ -189,6 +193,10 @@ import {
193 reappearLayoutEffects,
194 disconnectPassiveEffect,
195 reportUncaughtErrorInDEV,
196 + invokeLayoutEffectMountInDEV,
197 + invokePassiveEffectMountInDEV,
198 + invokeLayoutEffectUnmountInDEV,
199 + invokePassiveEffectUnmountInDEV,
200 } from './ReactFiberCommitWork.new';
201 import {enqueueUpdate} from './ReactFiberClassUpdateQueue.new';
202 import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -2570,7 +2578,7 @@ function commitRootImpl(
2578
2579 if (__DEV__ && enableStrictEffects) {
2580 if (!rootDidHavePassiveEffects) {
2573 - commitDoubleInvokeEffectsInDEV(root);
2581 + commitDoubleInvokeEffectsInDEV(root, false);
2582 }
2583 }
2584
@@ -2846,7 +2854,7 @@ function flushPassiveEffectsImpl() {
2854 }
2855
2856 if (__DEV__ && enableStrictEffects) {
2849 - commitDoubleInvokeEffectsInDEV(root);
2857 + commitDoubleInvokeEffectsInDEV(root, true);
2858 }
2859
2860 executionContext = prevExecutionContext;
@@ -3309,24 +3317,81 @@ function doubleInvokeEffectsInDEVIfNecessary(
3317 }
3318 }
3319
3312 -function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3320 +function commitDoubleInvokeEffectsInDEV(
3321 + root: FiberRoot,
3322 + hasPassiveEffects: boolean,
3323 +) {
3324 if (__DEV__ && enableStrictEffects) {
3314 - let doubleInvokeEffects = true;
3325 + if (useModernStrictMode) {
3326 + let doubleInvokeEffects = true;
3327
3316 - if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3317 - doubleInvokeEffects = false;
3328 + if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3329 + doubleInvokeEffects = false;
3330 + }
3331 + if (
3332 + root.tag === ConcurrentRoot &&
3333 + !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3334 + ) {
3335 + doubleInvokeEffects = false;
3336 + }
3337 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3338 + root,
3339 + root.current,
3340 + doubleInvokeEffects,
3341 + );
3342 + } else {
3343 + legacyCommitDoubleInvokeEffectsInDEV(root.current, hasPassiveEffects);
3344 }
3345 + }
3346 +}
3347 +
3348 +function legacyCommitDoubleInvokeEffectsInDEV(
3349 + fiber: Fiber,
3350 + hasPassiveEffects: boolean,
3351 +) {
3352 + // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3353 + // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3354 + // Maybe not a big deal since this is DEV only behavior.
3355 +
3356 + setCurrentDebugFiberInDEV(fiber);
3357 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3358 + if (hasPassiveEffects) {
3359 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV);
3360 + }
3361 +
3362 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3363 + if (hasPassiveEffects) {
3364 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3365 + }
3366 + resetCurrentDebugFiberInDEV();
3367 +}
3368 +
3369 +function invokeEffectsInDev(
3370 + firstChild: Fiber,
3371 + fiberFlags: Flags,
3372 + invokeEffectFn: (fiber: Fiber) => void,
3373 +) {
3374 + let current = firstChild;
3375 + let subtreeRoot = null;
3376 + while (current != null) {
3377 + const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3378 if (
3320 - root.tag === ConcurrentRoot &&
3321 - !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3379 + current !== subtreeRoot &&
3380 + current.child != null &&
3381 + primarySubtreeFlag !== NoFlags
3382 ) {
3323 - doubleInvokeEffects = false;
3383 + current = current.child;
3384 + } else {
3385 + if ((current.flags & fiberFlags) !== NoFlags) {
3386 + invokeEffectFn(current);
3387 + }
3388 +
3389 + if (current.sibling !== null) {
3390 + current = current.sibling;
3391 + } else {
3392 + current = subtreeRoot = current.return;
3393 + }
3394 }
3325 - recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3326 - root,
3327 - root.current,
3328 - doubleInvokeEffects,
3329 - );
3395 }
3396 }
3397
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+79 -14
@@ -40,6 +40,7 @@ import {
40 enableUpdaterTracking,
41 enableCache,
42 enableTransitionTracing,
43 + useModernStrictMode,
44 } from 'shared/ReactFeatureFlags';
45 import ReactSharedInternals from 'shared/ReactSharedInternals';
46 import is from 'shared/objectIs';
@@ -113,6 +114,7 @@ import {
114 Profiler,
115 } from './ReactWorkTags';
116 import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
117 +import type {Flags} from './ReactFiberFlags';
118 import {
119 NoFlags,
120 Incomplete,
@@ -125,6 +127,8 @@ import {
127 PassiveMask,
128 PlacementDEV,
129 Visibility,
130 + MountPassiveDev,
131 + MountLayoutDev,
132 } from './ReactFiberFlags';
133 import {
134 NoLanes,
@@ -189,6 +193,10 @@ import {
193 reappearLayoutEffects,
194 disconnectPassiveEffect,
195 reportUncaughtErrorInDEV,
196 + invokeLayoutEffectMountInDEV,
197 + invokePassiveEffectMountInDEV,
198 + invokeLayoutEffectUnmountInDEV,
199 + invokePassiveEffectUnmountInDEV,
200 } from './ReactFiberCommitWork.old';
201 import {enqueueUpdate} from './ReactFiberClassUpdateQueue.old';
202 import {resetContextDependencies} from './ReactFiberNewContext.old';
@@ -2570,7 +2578,7 @@ function commitRootImpl(
2578
2579 if (__DEV__ && enableStrictEffects) {
2580 if (!rootDidHavePassiveEffects) {
2573 - commitDoubleInvokeEffectsInDEV(root);
2581 + commitDoubleInvokeEffectsInDEV(root, false);
2582 }
2583 }
2584
@@ -2846,7 +2854,7 @@ function flushPassiveEffectsImpl() {
2854 }
2855
2856 if (__DEV__ && enableStrictEffects) {
2849 - commitDoubleInvokeEffectsInDEV(root);
2857 + commitDoubleInvokeEffectsInDEV(root, true);
2858 }
2859
2860 executionContext = prevExecutionContext;
@@ -3309,24 +3317,81 @@ function doubleInvokeEffectsInDEVIfNecessary(
3317 }
3318 }
3319
3312 -function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3320 +function commitDoubleInvokeEffectsInDEV(
3321 + root: FiberRoot,
3322 + hasPassiveEffects: boolean,
3323 +) {
3324 if (__DEV__ && enableStrictEffects) {
3314 - let doubleInvokeEffects = true;
3325 + if (useModernStrictMode) {
3326 + let doubleInvokeEffects = true;
3327
3316 - if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3317 - doubleInvokeEffects = false;
3328 + if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3329 + doubleInvokeEffects = false;
3330 + }
3331 + if (
3332 + root.tag === ConcurrentRoot &&
3333 + !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3334 + ) {
3335 + doubleInvokeEffects = false;
3336 + }
3337 + recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3338 + root,
3339 + root.current,
3340 + doubleInvokeEffects,
3341 + );
3342 + } else {
3343 + legacyCommitDoubleInvokeEffectsInDEV(root.current, hasPassiveEffects);
3344 }
3345 + }
3346 +}
3347 +
3348 +function legacyCommitDoubleInvokeEffectsInDEV(
3349 + fiber: Fiber,
3350 + hasPassiveEffects: boolean,
3351 +) {
3352 + // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3353 + // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3354 + // Maybe not a big deal since this is DEV only behavior.
3355 +
3356 + setCurrentDebugFiberInDEV(fiber);
3357 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3358 + if (hasPassiveEffects) {
3359 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV);
3360 + }
3361 +
3362 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3363 + if (hasPassiveEffects) {
3364 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3365 + }
3366 + resetCurrentDebugFiberInDEV();
3367 +}
3368 +
3369 +function invokeEffectsInDev(
3370 + firstChild: Fiber,
3371 + fiberFlags: Flags,
3372 + invokeEffectFn: (fiber: Fiber) => void,
3373 +) {
3374 + let current = firstChild;
3375 + let subtreeRoot = null;
3376 + while (current != null) {
3377 + const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3378 if (
3320 - root.tag === ConcurrentRoot &&
3321 - !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3379 + current !== subtreeRoot &&
3380 + current.child != null &&
3381 + primarySubtreeFlag !== NoFlags
3382 ) {
3323 - doubleInvokeEffects = false;
3383 + current = current.child;
3384 + } else {
3385 + if ((current.flags & fiberFlags) !== NoFlags) {
3386 + invokeEffectFn(current);
3387 + }
3388 +
3389 + if (current.sibling !== null) {
3390 + current = current.sibling;
3391 + } else {
3392 + current = subtreeRoot = current.return;
3393 + }
3394 }
3325 - recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3326 - root,
3327 - root.current,
3328 - doubleInvokeEffects,
3329 - );
3395 }
3396 }
3397
packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
+1 -1
@@ -55,7 +55,7 @@ describe('ReactOffscreenStrictMode', () => {
55 ]);
56 });
57
58 - // @gate __DEV__ && enableStrictEffects && enableOffscreen
58 + // @gate __DEV__ && enableStrictEffects && enableOffscreen && useModernStrictMode
59 it('should not trigger strict effects when offscreen is hidden', () => {
60 act(() => {
61 ReactNoop.render(
packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
+1
@@ -379,6 +379,7 @@ describe('StrictEffectsMode defaults', () => {
379 expect(Scheduler).toHaveYielded([]);
380 });
381
382 + //@gate useModernStrictMode
383 it('disconnects refs during double invoking', () => {
384 const onRefMock = jest.fn();
385 function App({text}) {
packages/react/src/__tests__/ReactStrictMode-test.js
+1 -1
@@ -67,7 +67,7 @@ describe('ReactStrictMode', () => {
67 );
68 });
69
70 - // @gate __DEV__ && !enableStrictEffects
70 + // @gate __DEV__
71 it('should invoke precommit lifecycle methods twice', () => {
72 let log = [];
73 let shouldComponentUpdate = false;
packages/shared/ReactFeatureFlags.js
+5
@@ -260,3 +260,8 @@ export const enableGetInspectorDataForInstanceInProduction = false;
260 export const enableProfilerNestedUpdateScheduledHook = false;
261
262 export const consoleManagedByDevToolsDuringStrictMode = true;
263 +
264 +// Modern <StrictMode /> behaviour aligns more with what components
265 +// components will encounter in production, especially when used With <Offscreen />.
266 +// TODO: clean up legacy <StrictMode /> once tests pass WWW.
267 +export const useModernStrictMode = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -84,6 +84,8 @@ export const enableUseMutableSource = true;
84 export const enableTransitionTracing = false;
85
86 export const enableFloat = false;
87 +
88 +export const useModernStrictMode = false;
89 // Flow magic to verify the exports of this file match the original version.
90 // eslint-disable-next-line no-unused-vars
91 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -73,6 +73,8 @@ export const enableUseMutableSource = false;
73 export const enableTransitionTracing = false;
74
75 export const enableFloat = false;
76 +
77 +export const useModernStrictMode = false;
78 // Flow magic to verify the exports of this file match the original version.
79 // eslint-disable-next-line no-unused-vars
80 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2
@@ -73,6 +73,8 @@ export const enableUseMutableSource = false;
73 export const enableTransitionTracing = false;
74
75 export const enableFloat = false;
76 +
77 +export const useModernStrictMode = false;
78 // Flow magic to verify the exports of this file match the original version.
79 // eslint-disable-next-line no-unused-vars
80 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2
@@ -71,6 +71,8 @@ export const enableUseMutableSource = false;
71 export const enableTransitionTracing = false;
72
73 export const enableFloat = false;
74 +
75 +export const useModernStrictMode = false;
76 // Flow magic to verify the exports of this file match the original version.
77 // eslint-disable-next-line no-unused-vars
78 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -75,6 +75,8 @@ export const enableUseMutableSource = true;
75 export const enableTransitionTracing = false;
76
77 export const enableFloat = false;
78 +
79 +export const useModernStrictMode = false;
80 // Flow magic to verify the exports of this file match the original version.
81 // eslint-disable-next-line no-unused-vars
82 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.js
+2
@@ -73,6 +73,8 @@ export const enableUseMutableSource = false;
73 export const enableTransitionTracing = false;
74
75 export const enableFloat = false;
76 +
77 +export const useModernStrictMode = false;
78 // Flow magic to verify the exports of this file match the original version.
79 // eslint-disable-next-line no-unused-vars
80 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+2
@@ -74,6 +74,8 @@ export const enableUseMutableSource = true;
74 export const enableTransitionTracing = false;
75
76 export const enableFloat = false;
77 +
78 +export const useModernStrictMode = false;
79 // Flow magic to verify the exports of this file match the original version.
80 // eslint-disable-next-line no-unused-vars
81 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -111,6 +111,7 @@ export const enableUseMutableSource = true;
111
112 export const enableCustomElementPropertySupport = __EXPERIMENTAL__;
113
114 +export const useModernStrictMode = false;
115 // Flow magic to verify the exports of this file match the original version.
116 // eslint-disable-next-line no-unused-vars
117 type Check<_X, Y: _X, X: Y = _X> = null;