Add back root override for strict mode (#21428)
* Add back root override for strict mode * Switch flag to boolean * Fix flow
Ricky committed
May 4, 2021 at 15:42 UTC
e9a4a44aae675e1b164cf2ae509e438c324d424a
13 files changed
+74
-5
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+14
@@ -102,6 +102,20 @@ describe('ReactTestUtils.act()', () => {
102
root.render(<App />);
103
Scheduler.unstable_flushAll();
104
});
105
+
106
+ // @gate experimental
107
+ it('warns in concurrent mode if root is strict', () => {
108
+ expect(() => {
109
+ const root = ReactDOM.unstable_createRoot(
110
+ document.createElement('div'),
111
+ {unstable_strictMode: true},
112
+ );
113
+ root.render(<App />);
114
+ Scheduler.unstable_flushAll();
115
+ }).toErrorDev([
116
+ 'An update to App ran an effect, but was not wrapped in act(...)',
117
+ ]);
118
+ });
119
});
120
});
121
packages/react-dom/src/client/ReactDOMRoot.js
+3
@@ -27,6 +27,7 @@ export type RootOptions = {
27
mutableSources?: Array<MutableSource<any>>,
28
...
29
},
30
+ unstable_strictMode?: boolean,
31
unstable_concurrentUpdatesByDefault?: boolean,
32
...
33
};
@@ -122,6 +123,7 @@ function createRootImpl(
123
options.hydrationOptions != null &&
124
options.hydrationOptions.mutableSources) ||
125
null;
126
+ const isStrictMode = options != null && options.unstable_strictMode === true;
127
128
let concurrentUpdatesByDefaultOverride = null;
129
if (allowConcurrentByDefault) {
@@ -136,6 +138,7 @@ function createRootImpl(
138
tag,
139
hydrate,
140
hydrationCallbacks,
141
+ isStrictMode,
142
concurrentUpdatesByDefaultOverride,
143
);
144
markContainerAsRoot(root.current, container);
packages/react-native-renderer/src/ReactFabric.js
+1
-1
@@ -207,7 +207,7 @@ function render(
207
if (!root) {
208
// TODO (bvaughn): If we decide to keep the wrapper component,
209
// We could create a wrapper for containerTag as well to reduce special casing.
210
- root = createContainer(containerTag, LegacyRoot, false, null, null);
210
+ root = createContainer(containerTag, LegacyRoot, false, null, false, null);
211
roots.set(containerTag, root);
212
}
213
updateContainer(element, root, null, callback);
packages/react-native-renderer/src/ReactNativeRenderer.js
+1
-1
@@ -203,7 +203,7 @@ function render(
203
if (!root) {
204
// TODO (bvaughn): If we decide to keep the wrapper component,
205
// We could create a wrapper for containerTag as well to reduce special casing.
206
- root = createContainer(containerTag, LegacyRoot, false, null, null);
206
+ root = createContainer(containerTag, LegacyRoot, false, null, false, null);
207
roots.set(containerTag, root);
208
}
209
updateContainer(element, root, null, callback);
packages/react-noop-renderer/src/createReactNoop.js
+3
-1
@@ -722,7 +722,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
722
if (!root) {
723
const container = {rootID: rootID, pendingChildren: [], children: []};
724
rootContainers.set(rootID, container);
725
- root = NoopRenderer.createContainer(container, tag, false, null);
725
+ root = NoopRenderer.createContainer(container, tag, false, null, null);
726
roots.set(rootID, root);
727
}
728
return root.current.stateNode.containerInfo;
@@ -740,6 +740,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
740
ConcurrentRoot,
741
false,
742
null,
743
+ null,
744
);
745
return {
746
_Scheduler: Scheduler,
@@ -766,6 +767,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
767
LegacyRoot,
768
false,
769
null,
770
+ null,
771
);
772
return {
773
_Scheduler: Scheduler,
packages/react-reconciler/src/ReactFiber.new.js
+8
-1
@@ -422,12 +422,19 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
422
423
export function createHostRootFiber(
424
tag: RootTag,
425
+ isStrictMode: boolean,
426
concurrentUpdatesByDefaultOverride: null | boolean,
427
): Fiber {
428
let mode;
429
if (tag === ConcurrentRoot) {
430
mode = ConcurrentMode;
430
- if (enableStrictEffects && createRootStrictEffectsByDefault) {
431
+ if (isStrictMode === true) {
432
+ mode |= StrictLegacyMode;
433
+
434
+ if (enableStrictEffects) {
435
+ mode |= StrictEffectsMode;
436
+ }
437
+ } else if (enableStrictEffects && createRootStrictEffectsByDefault) {
438
mode |= StrictLegacyMode | StrictEffectsMode;
439
}
440
if (
packages/react-reconciler/src/ReactFiber.old.js
+8
-1
@@ -422,12 +422,19 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
422
423
export function createHostRootFiber(
424
tag: RootTag,
425
+ isStrictMode: boolean,
426
concurrentUpdatesByDefaultOverride: null | boolean,
427
): Fiber {
428
let mode;
429
if (tag === ConcurrentRoot) {
430
mode = ConcurrentMode;
430
- if (enableStrictEffects && createRootStrictEffectsByDefault) {
431
+ if (isStrictMode === true) {
432
+ mode |= StrictLegacyMode;
433
+
434
+ if (enableStrictEffects) {
435
+ mode |= StrictEffectsMode;
436
+ }
437
+ } else if (enableStrictEffects && createRootStrictEffectsByDefault) {
438
mode |= StrictLegacyMode | StrictEffectsMode;
439
}
440
if (
packages/react-reconciler/src/ReactFiberReconciler.new.js
+2
@@ -248,6 +248,7 @@ export function createContainer(
248
tag: RootTag,
249
hydrate: boolean,
250
hydrationCallbacks: null | SuspenseHydrationCallbacks,
251
+ isStrictMode: boolean,
252
concurrentUpdatesByDefaultOverride: null | boolean,
253
): OpaqueRoot {
254
return createFiberRoot(
@@ -255,6 +256,7 @@ export function createContainer(
256
tag,
257
hydrate,
258
hydrationCallbacks,
259
+ isStrictMode,
260
concurrentUpdatesByDefaultOverride,
261
);
262
}
packages/react-reconciler/src/ReactFiberReconciler.old.js
+2
@@ -248,6 +248,7 @@ export function createContainer(
248
tag: RootTag,
249
hydrate: boolean,
250
hydrationCallbacks: null | SuspenseHydrationCallbacks,
251
+ isStrictMode: boolean,
252
concurrentUpdatesByDefaultOverride: null | boolean,
253
): OpaqueRoot {
254
return createFiberRoot(
@@ -255,6 +256,7 @@ export function createContainer(
256
tag,
257
hydrate,
258
hydrationCallbacks,
259
+ isStrictMode,
260
concurrentUpdatesByDefaultOverride,
261
);
262
}
packages/react-reconciler/src/ReactFiberRoot.new.js
+2
@@ -98,6 +98,7 @@ export function createFiberRoot(
98
tag: RootTag,
99
hydrate: boolean,
100
hydrationCallbacks: null | SuspenseHydrationCallbacks,
101
+ isStrictMode: boolean,
102
concurrentUpdatesByDefaultOverride: null | boolean,
103
): FiberRoot {
104
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
@@ -109,6 +110,7 @@ export function createFiberRoot(
110
// stateNode is any.
111
const uninitializedFiber = createHostRootFiber(
112
tag,
113
+ isStrictMode,
114
concurrentUpdatesByDefaultOverride,
115
);
116
root.current = uninitializedFiber;
packages/react-reconciler/src/ReactFiberRoot.old.js
+2
@@ -98,6 +98,7 @@ export function createFiberRoot(
98
tag: RootTag,
99
hydrate: boolean,
100
hydrationCallbacks: null | SuspenseHydrationCallbacks,
101
+ isStrictMode: boolean,
102
concurrentUpdatesByDefaultOverride: null | boolean,
103
): FiberRoot {
104
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
@@ -109,6 +110,7 @@ export function createFiberRoot(
110
// stateNode is any.
111
const uninitializedFiber = createHostRootFiber(
112
tag,
113
+ isStrictMode,
114
concurrentUpdatesByDefaultOverride,
115
);
116
root.current = uninitializedFiber;
packages/react-test-renderer/src/ReactTestRenderer.js
+6
@@ -58,6 +58,7 @@ const {IsSomeRendererActing} = ReactSharedInternals;
58
type TestRendererOptions = {
59
createNodeMock: (element: React$Element<any>) => any,
60
unstable_isConcurrent: boolean,
61
+ unstable_strictMode: boolean,
62
unstable_concurrentUpdatesByDefault: boolean,
63
...
64
};
@@ -436,6 +437,7 @@ function propsMatch(props: Object, filter: Object): boolean {
437
function create(element: React$Element<any>, options: TestRendererOptions) {
438
let createNodeMock = defaultTestOptions.createNodeMock;
439
let isConcurrent = false;
440
+ let isStrictMode = false;
441
let concurrentUpdatesByDefault = null;
442
if (typeof options === 'object' && options !== null) {
443
if (typeof options.createNodeMock === 'function') {
@@ -444,6 +446,9 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
446
if (options.unstable_isConcurrent === true) {
447
isConcurrent = true;
448
}
449
+ if (options.unstable_strictMode === true) {
450
+ isStrictMode = true;
451
+ }
452
if (allowConcurrentByDefault) {
453
if (options.unstable_concurrentUpdatesByDefault !== undefined) {
454
concurrentUpdatesByDefault =
@@ -461,6 +466,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
466
isConcurrent ? ConcurrentRoot : LegacyRoot,
467
false,
468
null,
469
+ isStrictMode,
470
concurrentUpdatesByDefault,
471
);
472
invariant(root != null, 'something went wrong');
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+22
@@ -65,6 +65,28 @@ describe('ReactStrictMode', () => {
65
});
66
67
if (__DEV__) {
68
+ // @gate experimental
69
+ it('should support enabling strict mode via createRoot option', () => {
70
+ act(() => {
71
+ const container = document.createElement('div');
72
+ const root = ReactDOM.createRoot(container, {
73
+ unstable_strictMode: true,
74
+ });
75
+ root.render(<Component label="A" />);
76
+ });
77
+
78
+ expect(log).toEqual([
79
+ 'A: render',
80
+ 'A: render',
81
+ 'A: useLayoutEffect mount',
82
+ 'A: useEffect mount',
83
+ 'A: useLayoutEffect unmount',
84
+ 'A: useEffect unmount',
85
+ 'A: useLayoutEffect mount',
86
+ 'A: useEffect mount',
87
+ ]);
88
+ });
89
+
90
// @gate experimental
91
it('should include legacy + strict effects mode', () => {
92
act(() => {