@samitouri / QOS-React-2 / commits / 254cbdbd6d

Add a temporary internal option to disable double useEffect in legacy strict mode (#26914)

<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> We are upgrading React 17 codebase to React18, and `StrictMode` has been great for surfacing potential production bugs on React18 for class components. There are non-trivial number of test failures caused by double `useEffect` in StrictMode. To prioritize surfacing and fixing issues that will break in production now, we need a flag to turn off double `useEffect` for now in StrictMode temporarily. This is a Meta-only hack for rolling out `createRoot` and we will fast follow to remove it and use full strict mode. ## How did you test this change? <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> jest

Tianyu Yao committed Jun 21, 2023 at 11:14 UTC 254cbdbd6d851a30bf3b649a6cb7c52786766fa4
13 files changed +77 -8
packages/react-reconciler/src/ReactFiber.js
+12
@@ -39,6 +39,7 @@ import {
39 enableDebugTracing,
40 enableFloat,
41 enableHostSingletons,
42 + enableDO_NOT_USE_disableStrictPassiveEffect,
43 } from 'shared/ReactFeatureFlags';
44 import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
45 import {ConcurrentRoot} from './ReactRootTags';
@@ -87,6 +88,7 @@ import {
88 StrictLegacyMode,
89 StrictEffectsMode,
90 ConcurrentUpdatesByDefaultMode,
91 + NoStrictPassiveEffectsMode,
92 } from './ReactTypeOfMode';
93 import {
94 REACT_FORWARD_REF_TYPE,
@@ -539,6 +541,12 @@ export function createFiberFromTypeAndProps(
541 if ((mode & ConcurrentMode) !== NoMode) {
542 // Strict effects should never run on legacy roots
543 mode |= StrictEffectsMode;
544 + if (
545 + enableDO_NOT_USE_disableStrictPassiveEffect &&
546 + pendingProps.DO_NOT_USE_disableStrictPassiveEffect
547 + ) {
548 + mode |= NoStrictPassiveEffectsMode;
549 + }
550 }
551 break;
552 case REACT_PROFILER_TYPE:
@@ -752,6 +760,10 @@ export function createFiberFromOffscreen(
760 lanes: Lanes,
761 key: null | string,
762 ): Fiber {
763 + if (__DEV__) {
764 + // StrictMode in Offscreen should always run double passive effects
765 + mode &= ~NoStrictPassiveEffectsMode;
766 + }
767 const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
768 fiber.elementType = REACT_OFFSCREEN_TYPE;
769 fiber.lanes = lanes;
packages/react-reconciler/src/ReactFiberHooks.js
+3 -1
@@ -58,6 +58,7 @@ import {
58 DebugTracingMode,
59 StrictEffectsMode,
60 StrictLegacyMode,
61 + NoStrictPassiveEffectsMode,
62 } from './ReactTypeOfMode';
63 import {
64 NoLane,
@@ -2257,7 +2258,8 @@ function mountEffect(
2258 ): void {
2259 if (
2260 __DEV__ &&
2260 - (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2261 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
2262 + (currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
2263 ) {
2264 mountEffectImpl(
2265 MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
packages/react-reconciler/src/ReactTypeOfMode.js
+8 -7
@@ -9,11 +9,12 @@
9
10 export type TypeOfMode = number;
11
12 -export const NoMode = /* */ 0b000000;
12 +export const NoMode = /* */ 0b0000000;
13 // TODO: Remove ConcurrentMode by reading from the root tag instead
14 -export const ConcurrentMode = /* */ 0b000001;
15 -export const ProfileMode = /* */ 0b000010;
16 -export const DebugTracingMode = /* */ 0b000100;
17 -export const StrictLegacyMode = /* */ 0b001000;
18 -export const StrictEffectsMode = /* */ 0b010000;
19 -export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000;
14 +export const ConcurrentMode = /* */ 0b0000001;
15 +export const ProfileMode = /* */ 0b0000010;
16 +export const DebugTracingMode = /* */ 0b0000100;
17 +export const StrictLegacyMode = /* */ 0b0001000;
18 +export const StrictEffectsMode = /* */ 0b0010000;
19 +export const ConcurrentUpdatesByDefaultMode = /* */ 0b0100000;
20 +export const NoStrictPassiveEffectsMode = /* */ 0b1000000;
packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
+24
@@ -55,6 +55,30 @@ describe('ReactOffscreenStrictMode', () => {
55 ]);
56 });
57
58 + // @gate __DEV__ && enableOffscreen
59 + it('should trigger strict effects when disableStrictPassiveEffect is presented on StrictMode', async () => {
60 + await act(() => {
61 + ReactNoop.render(
62 + <React.StrictMode DO_NOT_USE_disableStrictPassiveEffect={true}>
63 + <Offscreen>
64 + <Component label="A" />
65 + </Offscreen>
66 + </React.StrictMode>,
67 + );
68 + });
69 +
70 + expect(log).toEqual([
71 + 'A: render',
72 + 'A: render',
73 + 'A: useLayoutEffect mount',
74 + 'A: useEffect mount',
75 + 'A: useLayoutEffect unmount',
76 + 'A: useEffect unmount',
77 + 'A: useLayoutEffect mount',
78 + 'A: useEffect mount',
79 + ]);
80 + });
81 +
82 // @gate __DEV__ && enableOffscreen && useModernStrictMode
83 it('should not trigger strict effects when offscreen is hidden', async () => {
84 await act(() => {
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+22
@@ -104,6 +104,28 @@ describe('ReactStrictMode', () => {
104 ]);
105 });
106
107 + // @gate enableDO_NOT_USE_disableStrictPassiveEffect
108 + it('should include legacy + strict effects mode, but not strict passive effect with disableStrictPassiveEffect', async () => {
109 + await act(() => {
110 + const container = document.createElement('div');
111 + const root = ReactDOMClient.createRoot(container);
112 + root.render(
113 + <React.StrictMode DO_NOT_USE_disableStrictPassiveEffect={true}>
114 + <Component label="A" />
115 + </React.StrictMode>,
116 + );
117 + });
118 +
119 + expect(log).toEqual([
120 + 'A: render',
121 + 'A: render',
122 + 'A: useLayoutEffect mount',
123 + 'A: useEffect mount',
124 + 'A: useLayoutEffect unmount',
125 + 'A: useLayoutEffect mount',
126 + ]);
127 + });
128 +
129 it('should allow level to be increased with nesting', async () => {
130 await act(() => {
131 const container = document.createElement('div');
packages/shared/ReactFeatureFlags.js
+1
@@ -237,3 +237,4 @@ export const consoleManagedByDevToolsDuringStrictMode = true;
237 // components will encounter in production, especially when used With <Offscreen />.
238 // TODO: clean up legacy <StrictMode /> once tests pass WWW.
239 export const useModernStrictMode = false;
240 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -82,6 +82,7 @@ export const enableFloat = true;
82 export const enableHostSingletons = true;
83
84 export const useModernStrictMode = false;
85 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
86 export const enableFizzExternalRuntime = false;
87
88 export const diffInCommitPhase = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -68,6 +68,7 @@ export const enableFloat = true;
68 export const enableHostSingletons = true;
69
70 export const useModernStrictMode = false;
71 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
72 export const enableFizzExternalRuntime = false;
73 export const enableDeferRootSchedulingToMicrotask = true;
74
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -68,6 +68,7 @@ export const enableFloat = true;
68 export const enableHostSingletons = true;
69
70 export const useModernStrictMode = false;
71 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
72 export const enableFizzExternalRuntime = false;
73 export const enableDeferRootSchedulingToMicrotask = true;
74
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -66,6 +66,7 @@ export const enableFloat = true;
66 export const enableHostSingletons = true;
67
68 export const useModernStrictMode = false;
69 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
70 export const enableDeferRootSchedulingToMicrotask = true;
71
72 export const diffInCommitPhase = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -70,6 +70,7 @@ export const enableFloat = true;
70 export const enableHostSingletons = true;
71
72 export const useModernStrictMode = false;
73 +export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
74 export const enableFizzExternalRuntime = false;
75 export const enableDeferRootSchedulingToMicrotask = true;
76
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -28,6 +28,7 @@ export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
28 export const diffInCommitPhase = __VARIANT__;
29 export const enableAsyncActions = __VARIANT__;
30 export const alwaysThrottleRetries = __VARIANT__;
31 +export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
32
33 // Enable this flag to help with concurrent mode debugging.
34 // It logs information to the console about React scheduling, rendering, and commit phases.
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -30,6 +30,7 @@ export const {
30 diffInCommitPhase,
31 enableAsyncActions,
32 alwaysThrottleRetries,
33 + enableDO_NOT_USE_disableStrictPassiveEffect,
34 } = dynamicFeatureFlags;
35
36 // On WWW, __EXPERIMENTAL__ is used for a new modern build.