Use the disableLegacyMode where ever we check the ConcurrentMode mode (#28657)
Saves some bytes and ensures that we're actually disabling it. Turns out this flag wasn't disabling React Native/Fabric, React Noop and React ART legacy modes so those are updated too. Should be rebased on #28681.
Sebastian Markbåge committed
Apr 2, 2024 at 21:07 UTC
5de8703646cdd3838cb1686f761b10c0692743aa
33 files changed
+288
-149
packages/react-native-renderer/src/ReactFabric.js
+5
@@ -48,6 +48,7 @@ import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFab
48
49
// Module provided by RN:
50
import {ReactFiberErrorDialog} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
51
+import {disableLegacyMode} from 'shared/ReactFeatureFlags';
52
53
if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
54
throw new Error(
@@ -106,6 +107,10 @@ function render(
107
callback: ?() => void,
108
concurrentRoot: ?boolean,
109
): ?ElementRef<ElementType> {
110
+ if (disableLegacyMode && !concurrentRoot) {
111
+ throw new Error('render: Unsupported Legacy Mode API.');
112
+ }
113
+
114
let root = roots.get(containerTag);
115
116
if (!root) {
packages/react-native-renderer/src/ReactNativeRenderer.js
+6
@@ -50,6 +50,8 @@ import {
50
isChildPublicInstance,
51
} from './ReactNativePublicCompat';
52
53
+import {disableLegacyMode} from 'shared/ReactFeatureFlags';
54
+
55
// Module provided by RN:
56
import {ReactFiberErrorDialog} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
57
@@ -109,6 +111,10 @@ function render(
111
containerTag: number,
112
callback: ?() => void,
113
): ?ElementRef<ElementType> {
114
+ if (disableLegacyMode) {
115
+ throw new Error('render: Unsupported Legacy Mode API.');
116
+ }
117
+
118
let root = roots.get(containerTag);
119
120
if (!root) {
packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
+1
@@ -50,6 +50,7 @@ describe('ReactNativeError', () => {
50
);
51
});
52
53
+ // @gate !disableLegacyMode
54
it('should be able to extract a component stack from a native view', () => {
55
const View = createReactNativeComponentClass('View', () => ({
56
validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
+16
-2
@@ -79,6 +79,7 @@ beforeEach(() => {
79
.ReactNativeViewConfigRegistry.register;
80
});
81
82
+// @gate !disableLegacyMode
83
it('fails to register the same event name with different types', async () => {
84
const InvalidEvents = createReactNativeComponentClass('InvalidEvents', () => {
85
if (!__DEV__) {
@@ -122,6 +123,7 @@ it('fails to register the same event name with different types', async () => {
123
).rejects.toThrow('Event cannot be both direct and bubbling: topChange');
124
});
125
126
+// @gate !disableLegacyMode
127
it('fails if unknown/unsupported event types are dispatched', () => {
128
expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1);
129
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
@@ -129,7 +131,10 @@ it('fails if unknown/unsupported event types are dispatched', () => {
131
132
ReactNative.render(<View onUnspecifiedEvent={() => {}} />, 1);
133
132
- expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
134
+ expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchInlineSnapshot(`
135
+ "<native root> {}
136
+ View null"
137
+ `);
138
expect(UIManager.createView).toHaveBeenCalledTimes(1);
139
140
const target = UIManager.createView.mock.calls[0][0];
@@ -143,6 +148,7 @@ it('fails if unknown/unsupported event types are dispatched', () => {
148
}).toThrow('Unsupported top level event type "unspecifiedEvent" dispatched');
149
});
150
151
+// @gate !disableLegacyMode
152
it('handles events', () => {
153
expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1);
154
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
@@ -167,7 +173,11 @@ it('handles events', () => {
173
1,
174
);
175
170
- expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
176
+ expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchInlineSnapshot(`
177
+ "<native root> {}
178
+ View {"foo":"outer"}
179
+ View {"foo":"inner"}"
180
+ `);
181
expect(UIManager.createView).toHaveBeenCalledTimes(2);
182
183
// Don't depend on the order of createView() calls.
@@ -200,6 +210,7 @@ it('handles events', () => {
210
});
211
212
// @gate !disableLegacyContext || !__DEV__
213
+// @gate !disableLegacyMode
214
it('handles events on text nodes', () => {
215
expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1);
216
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
@@ -283,6 +294,7 @@ it('handles events on text nodes', () => {
294
]);
295
});
296
297
+// @gate !disableLegacyMode
298
it('handles when a responder is unmounted while a touch sequence is in progress', () => {
299
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
300
const View = fakeRequireNativeComponent('View', {id: true});
@@ -372,6 +384,7 @@ it('handles when a responder is unmounted while a touch sequence is in progress'
384
expect(log).toEqual(['two responder start']);
385
});
386
387
+// @gate !disableLegacyMode
388
it('handles events without target', () => {
389
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
390
@@ -462,6 +475,7 @@ it('handles events without target', () => {
475
]);
476
});
477
478
+// @gate !disableLegacyMode
479
it('dispatches event with target as instance', () => {
480
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
481
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+72
-2
@@ -45,6 +45,7 @@ describe('ReactNative', () => {
45
require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface').TextInputState;
46
});
47
48
+ // @gate !disableLegacyMode
49
it('should be able to create and render a native component', () => {
50
const View = createReactNativeComponentClass('RCTView', () => ({
51
validAttributes: {foo: true},
@@ -58,6 +59,7 @@ describe('ReactNative', () => {
59
expect(UIManager.updateView).not.toBeCalled();
60
});
61
62
+ // @gate !disableLegacyMode
63
it('should be able to create and update a native component', () => {
64
const View = createReactNativeComponentClass('RCTView', () => ({
65
validAttributes: {foo: true},
@@ -79,6 +81,7 @@ describe('ReactNative', () => {
81
expect(UIManager.updateView).toBeCalledWith(3, 'RCTView', {foo: 'bar'});
82
});
83
84
+ // @gate !disableLegacyMode
85
it('should not call UIManager.updateView after render for properties that have not changed', () => {
86
const Text = createReactNativeComponentClass('RCTText', () => ({
87
validAttributes: {foo: true},
@@ -105,6 +108,7 @@ describe('ReactNative', () => {
108
expect(UIManager.updateView).toHaveBeenCalledTimes(4);
109
});
110
111
+ // @gate !disableLegacyMode
112
it('should call dispatchCommand for native refs', () => {
113
const View = createReactNativeComponentClass('RCTView', () => ({
114
validAttributes: {foo: true},
@@ -133,6 +137,7 @@ describe('ReactNative', () => {
137
);
138
});
139
140
+ // @gate !disableLegacyMode
141
it('should warn and no-op if calling dispatchCommand on non native refs', () => {
142
class BasicClass extends React.Component {
143
render() {
@@ -162,6 +167,7 @@ describe('ReactNative', () => {
167
expect(UIManager.dispatchViewManagerCommand).not.toBeCalled();
168
});
169
170
+ // @gate !disableLegacyMode
171
it('should call sendAccessibilityEvent for native refs', () => {
172
const View = createReactNativeComponentClass('RCTView', () => ({
173
validAttributes: {foo: true},
@@ -192,6 +198,7 @@ describe('ReactNative', () => {
198
).toHaveBeenCalledWith(expect.any(Number), 'focus');
199
});
200
201
+ // @gate !disableLegacyMode
202
it('should warn and no-op if calling sendAccessibilityEvent on non native refs', () => {
203
class BasicClass extends React.Component {
204
render() {
@@ -221,6 +228,7 @@ describe('ReactNative', () => {
228
expect(UIManager.sendAccessibilityEvent).not.toBeCalled();
229
});
230
231
+ // @gate !disableLegacyMode
232
it('should not call UIManager.updateView from ref.setNativeProps for properties that have not changed', () => {
233
const View = createReactNativeComponentClass('RCTView', () => ({
234
validAttributes: {foo: true},
@@ -254,6 +262,7 @@ describe('ReactNative', () => {
262
);
263
});
264
265
+ // @gate !disableLegacyMode
266
it('should call UIManager.measure on ref.measure', () => {
267
const View = createReactNativeComponentClass('RCTView', () => ({
268
validAttributes: {foo: true},
@@ -280,6 +289,7 @@ describe('ReactNative', () => {
289
expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100, 0, 0);
290
});
291
292
+ // @gate !disableLegacyMode
293
it('should call UIManager.measureInWindow on ref.measureInWindow', () => {
294
const View = createReactNativeComponentClass('RCTView', () => ({
295
validAttributes: {foo: true},
@@ -306,6 +316,7 @@ describe('ReactNative', () => {
316
expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100);
317
});
318
319
+ // @gate !disableLegacyMode
320
it('should support reactTag in ref.measureLayout', () => {
321
const View = createReactNativeComponentClass('RCTView', () => ({
322
validAttributes: {foo: true},
@@ -346,6 +357,7 @@ describe('ReactNative', () => {
357
expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100);
358
});
359
360
+ // @gate !disableLegacyMode
361
it('should support ref in ref.measureLayout of host components', () => {
362
const View = createReactNativeComponentClass('RCTView', () => ({
363
validAttributes: {foo: true},
@@ -382,6 +394,7 @@ describe('ReactNative', () => {
394
expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100);
395
});
396
397
+ // @gate !disableLegacyMode
398
it('returns the correct instance and calls it in the callback', () => {
399
const View = createReactNativeComponentClass('RCTView', () => ({
400
validAttributes: {foo: true},
@@ -403,6 +416,7 @@ describe('ReactNative', () => {
416
expect(a).toBe(c);
417
});
418
419
+ // @gate !disableLegacyMode
420
it('renders and reorders children', () => {
421
const View = createReactNativeComponentClass('RCTView', () => ({
422
validAttributes: {title: true},
@@ -427,12 +441,59 @@ describe('ReactNative', () => {
441
const after = 'mxhpgwfralkeoivcstzy';
442
443
ReactNative.render(<Component chars={before} />, 11);
430
- expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
444
+ expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchInlineSnapshot(`
445
+ "<native root> {}
446
+ RCTView null
447
+ RCTView {"title":"a"}
448
+ RCTView {"title":"b"}
449
+ RCTView {"title":"c"}
450
+ RCTView {"title":"d"}
451
+ RCTView {"title":"e"}
452
+ RCTView {"title":"f"}
453
+ RCTView {"title":"g"}
454
+ RCTView {"title":"h"}
455
+ RCTView {"title":"i"}
456
+ RCTView {"title":"j"}
457
+ RCTView {"title":"k"}
458
+ RCTView {"title":"l"}
459
+ RCTView {"title":"m"}
460
+ RCTView {"title":"n"}
461
+ RCTView {"title":"o"}
462
+ RCTView {"title":"p"}
463
+ RCTView {"title":"q"}
464
+ RCTView {"title":"r"}
465
+ RCTView {"title":"s"}
466
+ RCTView {"title":"t"}"
467
+ `);
468
469
ReactNative.render(<Component chars={after} />, 11);
433
- expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
470
+ expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchInlineSnapshot(`
471
+ "<native root> {}
472
+ RCTView null
473
+ RCTView {"title":"m"}
474
+ RCTView {"title":"x"}
475
+ RCTView {"title":"h"}
476
+ RCTView {"title":"p"}
477
+ RCTView {"title":"g"}
478
+ RCTView {"title":"w"}
479
+ RCTView {"title":"f"}
480
+ RCTView {"title":"r"}
481
+ RCTView {"title":"a"}
482
+ RCTView {"title":"l"}
483
+ RCTView {"title":"k"}
484
+ RCTView {"title":"e"}
485
+ RCTView {"title":"o"}
486
+ RCTView {"title":"i"}
487
+ RCTView {"title":"v"}
488
+ RCTView {"title":"c"}
489
+ RCTView {"title":"s"}
490
+ RCTView {"title":"t"}
491
+ RCTView {"title":"z"}
492
+ RCTView {"title":"y"}"
493
+ `);
494
});
495
496
+ // @gate !disableLegacyMode
497
it('calls setState with no arguments', () => {
498
let mockArgs;
499
class Component extends React.Component {
@@ -448,6 +509,7 @@ describe('ReactNative', () => {
509
expect(mockArgs.length).toEqual(0);
510
});
511
512
+ // @gate !disableLegacyMode
513
it('should not throw when <View> is used inside of a <Text> ancestor', () => {
514
const Image = createReactNativeComponentClass('RCTImage', () => ({
515
validAttributes: {},
@@ -478,6 +540,7 @@ describe('ReactNative', () => {
540
);
541
});
542
543
+ // @gate !disableLegacyMode
544
it('should throw for text not inside of a <Text> ancestor', async () => {
545
const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
546
validAttributes: {},
@@ -512,6 +575,7 @@ describe('ReactNative', () => {
575
);
576
});
577
578
+ // @gate !disableLegacyMode
579
it('should not throw for text inside of an indirect <Text> ancestor', () => {
580
const Text = createReactNativeComponentClass('RCTText', () => ({
581
validAttributes: {},
@@ -528,6 +592,7 @@ describe('ReactNative', () => {
592
);
593
});
594
595
+ // @gate !disableLegacyMode
596
it('findHostInstance_DEPRECATED should warn if used to find a host component inside StrictMode', () => {
597
const View = createReactNativeComponentClass('RCTView', () => ({
598
validAttributes: {foo: true},
@@ -564,6 +629,7 @@ describe('ReactNative', () => {
629
expect(match).toBe(child);
630
});
631
632
+ // @gate !disableLegacyMode
633
it('findHostInstance_DEPRECATED should warn if passed a component that is inside StrictMode', () => {
634
const View = createReactNativeComponentClass('RCTView', () => ({
635
validAttributes: {foo: true},
@@ -601,6 +667,7 @@ describe('ReactNative', () => {
667
expect(match).toBe(child);
668
});
669
670
+ // @gate !disableLegacyMode
671
it('findNodeHandle should warn if used to find a host component inside StrictMode', () => {
672
const View = createReactNativeComponentClass('RCTView', () => ({
673
validAttributes: {foo: true},
@@ -635,6 +702,7 @@ describe('ReactNative', () => {
702
expect(match).toBe(child._nativeTag);
703
});
704
705
+ // @gate !disableLegacyMode
706
it('findNodeHandle should warn if passed a component that is inside StrictMode', () => {
707
const View = createReactNativeComponentClass('RCTView', () => ({
708
validAttributes: {foo: true},
@@ -670,6 +738,7 @@ describe('ReactNative', () => {
738
expect(match).toBe(child._nativeTag);
739
});
740
741
+ // @gate !disableLegacyMode
742
it('blur on host component calls TextInputState', () => {
743
const View = createReactNativeComponentClass('RCTView', () => ({
744
validAttributes: {foo: true},
@@ -687,6 +756,7 @@ describe('ReactNative', () => {
756
expect(TextInputState.blurTextInput).toHaveBeenCalledWith(viewRef.current);
757
});
758
759
+ // @gate !disableLegacyMode
760
it('focus on host component calls TextInputState', () => {
761
const View = createReactNativeComponentClass('RCTView', () => ({
762
validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/__snapshots__/ReactNativeEvents-test.internal.js.snap
deleted
-12
@@ -1,12 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`fails if unknown/unsupported event types are dispatched 1`] = `
4
-"<native root> {}
5
- View null"
6
-`;
7
-
8
-exports[`handles events 1`] = `
9
-"<native root> {}
10
- View {"foo":"outer"}
11
- View {"foo":"inner"}"
12
-`;
packages/react-native-renderer/src/__tests__/__snapshots__/ReactNativeMount-test.internal.js.snap
deleted
-51
@@ -1,51 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`ReactNative renders and reorders children 1`] = `
4
-"<native root> {}
5
- RCTView null
6
- RCTView {"title":"a"}
7
- RCTView {"title":"b"}
8
- RCTView {"title":"c"}
9
- RCTView {"title":"d"}
10
- RCTView {"title":"e"}
11
- RCTView {"title":"f"}
12
- RCTView {"title":"g"}
13
- RCTView {"title":"h"}
14
- RCTView {"title":"i"}
15
- RCTView {"title":"j"}
16
- RCTView {"title":"k"}
17
- RCTView {"title":"l"}
18
- RCTView {"title":"m"}
19
- RCTView {"title":"n"}
20
- RCTView {"title":"o"}
21
- RCTView {"title":"p"}
22
- RCTView {"title":"q"}
23
- RCTView {"title":"r"}
24
- RCTView {"title":"s"}
25
- RCTView {"title":"t"}"
26
-`;
27
-
28
-exports[`ReactNative renders and reorders children 2`] = `
29
-"<native root> {}
30
- RCTView null
31
- RCTView {"title":"m"}
32
- RCTView {"title":"x"}
33
- RCTView {"title":"h"}
34
- RCTView {"title":"p"}
35
- RCTView {"title":"g"}
36
- RCTView {"title":"w"}
37
- RCTView {"title":"f"}
38
- RCTView {"title":"r"}
39
- RCTView {"title":"a"}
40
- RCTView {"title":"l"}
41
- RCTView {"title":"k"}
42
- RCTView {"title":"e"}
43
- RCTView {"title":"o"}
44
- RCTView {"title":"i"}
45
- RCTView {"title":"v"}
46
- RCTView {"title":"c"}
47
- RCTView {"title":"s"}
48
- RCTView {"title":"t"}
49
- RCTView {"title":"z"}
50
- RCTView {"title":"y"}"
51
-`;
packages/react-native-renderer/src/__tests__/createReactNativeComponentClass-test.internal.js
+1
@@ -25,6 +25,7 @@ describe('createReactNativeComponentClass', () => {
25
ReactNative = require('react-native-renderer');
26
});
27
28
+ // @gate !disableLegacyMode
29
it('should register viewConfigs', () => {
30
const textViewConfig = {
31
validAttributes: {},
packages/react-noop-renderer/src/createReactNoop.js
+8
-1
@@ -32,7 +32,7 @@ import {
32
ConcurrentRoot,
33
LegacyRoot,
34
} from 'react-reconciler/constants';
35
-import {enableRefAsProp} from 'shared/ReactFeatureFlags';
35
+import {enableRefAsProp, disableLegacyMode} from 'shared/ReactFeatureFlags';
36
37
type Container = {
38
rootID: string,
@@ -1020,6 +1020,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1020
},
1021
1022
createLegacyRoot() {
1023
+ if (disableLegacyMode) {
1024
+ throw new Error('createLegacyRoot: Unsupported Legacy Mode API.');
1025
+ }
1026
+
1027
const container = {
1028
rootID: '' + idCounter++,
1029
pendingChildren: [],
@@ -1119,6 +1123,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1123
},
1124
1125
renderLegacySyncRoot(element: React$Element<any>, callback: ?Function) {
1126
+ if (disableLegacyMode) {
1127
+ throw new Error('createLegacyRoot: Unsupported Legacy Mode API.');
1128
+ }
1129
const rootID = DEFAULT_ROOT_ID;
1130
const container = ReactNoop.getOrCreateRootContainer(rootID, LegacyRoot);
1131
const root = roots.get(container.rootID);
packages/react-reconciler/src/ReactFiber.js
+3
-2
@@ -37,6 +37,7 @@ import {
37
enableDebugTracing,
38
enableDO_NOT_USE_disableStrictPassiveEffect,
39
enableRenderableContext,
40
+ disableLegacyMode,
41
} from 'shared/ReactFeatureFlags';
42
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
43
import {ConcurrentRoot} from './ReactRootTags';
@@ -439,7 +440,7 @@ export function createHostRootFiber(
440
concurrentUpdatesByDefaultOverride: null | boolean,
441
): Fiber {
442
let mode;
442
- if (tag === ConcurrentRoot) {
443
+ if (disableLegacyMode || tag === ConcurrentRoot) {
444
mode = ConcurrentMode;
445
if (isStrictMode === true) {
446
mode |= StrictLegacyMode | StrictEffectsMode;
@@ -517,7 +518,7 @@ export function createFiberFromTypeAndProps(
518
case REACT_STRICT_MODE_TYPE:
519
fiberTag = Mode;
520
mode |= StrictLegacyMode;
520
- if ((mode & ConcurrentMode) !== NoMode) {
521
+ if (disableLegacyMode || (mode & ConcurrentMode) !== NoMode) {
522
// Strict effects should never run on legacy roots
523
mode |= StrictEffectsMode;
524
if (
packages/react-reconciler/src/ReactFiberBeginWork.js
+17
-5
@@ -108,6 +108,7 @@ import {
108
enablePostpone,
109
enableRenderableContext,
110
enableRefAsProp,
111
+ disableLegacyMode,
112
} from 'shared/ReactFeatureFlags';
113
import isArray from 'shared/isArray';
114
import shallowEqual from 'shared/shallowEqual';
@@ -700,7 +701,10 @@ function updateOffscreenComponent(
701
);
702
}
703
703
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
704
+ if (
705
+ !disableLegacyMode &&
706
+ (workInProgress.mode & ConcurrentMode) === NoMode
707
+ ) {
708
// In legacy sync mode, don't defer the subtree. Render it now.
709
// TODO: Consider how Offscreen should work with transitions in the future
710
const nextState: OffscreenState = {
@@ -2347,6 +2351,7 @@ function mountSuspenseFallbackChildren(
2351
let primaryChildFragment;
2352
let fallbackChildFragment;
2353
if (
2354
+ !disableLegacyMode &&
2355
(mode & ConcurrentMode) === NoMode &&
2356
progressedPrimaryFragment !== null
2357
) {
@@ -2430,7 +2435,7 @@ function updateSuspensePrimaryChildren(
2435
children: primaryChildren,
2436
},
2437
);
2433
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
2438
+ if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
2439
primaryChildFragment.lanes = renderLanes;
2440
}
2441
primaryChildFragment.return = workInProgress;
@@ -2471,6 +2476,7 @@ function updateSuspenseFallbackChildren(
2476
if (
2477
// In legacy mode, we commit the primary tree as if it successfully
2478
// completed, even though it's in an inconsistent state.
2479
+ !disableLegacyMode &&
2480
(mode & ConcurrentMode) === NoMode &&
2481
// Make sure we're on the second pass, i.e. the primary child fragment was
2482
// already cloned. In legacy mode, the only case where this isn't true is
@@ -2607,7 +2613,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
2613
primaryChildFragment.sibling = fallbackChildFragment;
2614
workInProgress.child = primaryChildFragment;
2615
2610
- if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
2616
+ if (disableLegacyMode || (workInProgress.mode & ConcurrentMode) !== NoMode) {
2617
// We will have dropped the effect list which contains the
2618
// deletion. We need to reconcile to delete the current child.
2619
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
@@ -3195,7 +3201,7 @@ function updateSuspenseListComponent(
3201
}
3202
pushSuspenseListContext(workInProgress, suspenseContext);
3203
3198
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
3204
+ if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
3205
// In legacy mode, SuspenseList doesn't work so we just
3206
// use make it a noop by treating it as the default revealOrder.
3207
workInProgress.memoizedState = null;
@@ -3443,7 +3449,7 @@ function resetSuspendedCurrentOnMountInLegacyMode(
3449
current: null | Fiber,
3450
workInProgress: Fiber,
3451
) {
3446
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
3452
+ if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
3453
if (current !== null) {
3454
// A lazy component only mounts if it suspended inside a non-
3455
// concurrent tree, in an inconsistent state. We want to treat it like
@@ -4013,6 +4019,9 @@ function beginWork(
4019
);
4020
}
4021
case IncompleteClassComponent: {
4022
+ if (disableLegacyMode) {
4023
+ break;
4024
+ }
4025
const Component = workInProgress.type;
4026
const unresolvedProps = workInProgress.pendingProps;
4027
const resolvedProps =
@@ -4028,6 +4037,9 @@ function beginWork(
4037
);
4038
}
4039
case IncompleteFunctionComponent: {
4040
+ if (disableLegacyMode) {
4041
+ break;
4042
+ }
4043
const Component = workInProgress.type;
4044
const unresolvedProps = workInProgress.pendingProps;
4045
const resolvedProps =
packages/react-reconciler/src/ReactFiberCommitWork.js
+11
-6
@@ -54,6 +54,7 @@ import {
54
enableUseEffectEventHook,
55
enableLegacyHidden,
56
disableStringRefs,
57
+ disableLegacyMode,
58
} from 'shared/ReactFeatureFlags';
59
import {
60
FunctionComponent,
@@ -1164,7 +1165,8 @@ function commitLayoutEffectOnFiber(
1165
break;
1166
}
1167
case OffscreenComponent: {
1167
- const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
1168
+ const isModernRoot =
1169
+ disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;
1170
if (isModernRoot) {
1171
const isHidden = finishedWork.memoizedState !== null;
1172
const newOffscreenSubtreeIsHidden =
@@ -2255,7 +2257,7 @@ function commitDeletionEffectsOnFiber(
2257
}
2258
case OffscreenComponent: {
2259
safelyDetachRef(deletedFiber, nearestMountedAncestor);
2258
- if (deletedFiber.mode & ConcurrentMode) {
2260
+ if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {
2261
// If this offscreen component is hidden, we already unmounted it. Before
2262
// deleting the children, track that it's already unmounted so that we
2263
// don't attempt to unmount the effects again.
@@ -2932,7 +2934,7 @@ function commitMutationEffectsOnFiber(
2934
const isHidden = newState !== null;
2935
const wasHidden = current !== null && current.memoizedState !== null;
2936
2935
- if (finishedWork.mode & ConcurrentMode) {
2937
+ if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
2938
// Before committing the children, track on the stack whether this
2939
// offscreen subtree was already hidden, so that we don't unmount the
2940
// effects again.
@@ -2978,7 +2980,10 @@ function commitMutationEffectsOnFiber(
2980
// - This Offscreen was not hidden before.
2981
// - Ancestor Offscreen was not hidden in previous commit.
2982
if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) {
2981
- if ((finishedWork.mode & ConcurrentMode) !== NoMode) {
2983
+ if (
2984
+ disableLegacyMode ||
2985
+ (finishedWork.mode & ConcurrentMode) !== NoMode
2986
+ ) {
2987
// Disappear the layout effects of all the children
2988
recursivelyTraverseDisappearLayoutEffects(finishedWork);
2989
}
@@ -3676,7 +3681,7 @@ function commitPassiveMountOnFiber(
3681
committedTransitions,
3682
);
3683
} else {
3679
- if (finishedWork.mode & ConcurrentMode) {
3684
+ if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
3685
// The effects are currently disconnected. Since the tree is hidden,
3686
// don't connect them. This also applies to the initial render.
3687
if (enableCache || enableTransitionTracing) {
@@ -3874,7 +3879,7 @@ export function reconnectPassiveEffects(
3879
includeWorkInProgressEffects,
3880
);
3881
} else {
3877
- if (finishedWork.mode & ConcurrentMode) {
3882
+ if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
3883
// The effects are currently disconnected. Since the tree is hidden,
3884
// don't connect them. This also applies to the initial render.
3885
if (enableCache || enableTransitionTracing) {
packages/react-reconciler/src/ReactFiberCompleteWork.js
+15
-2
@@ -40,6 +40,7 @@ import {
40
enableTransitionTracing,
41
enableRenderableContext,
42
passChildrenWhenCloningPersistedNodes,
43
+ disableLegacyMode,
44
} from 'shared/ReactFeatureFlags';
45
46
import {now} from './Scheduler';
@@ -949,10 +950,15 @@ function completeWork(
950
// for hydration.
951
popTreeContext(workInProgress);
952
switch (workInProgress.tag) {
953
+ case IncompleteFunctionComponent: {
954
+ if (disableLegacyMode) {
955
+ break;
956
+ }
957
+ // Fallthrough
958
+ }
959
case LazyComponent:
960
case SimpleMemoComponent:
961
case FunctionComponent:
955
- case IncompleteFunctionComponent:
962
case ForwardRef:
963
case Fragment:
964
case Mode:
@@ -1475,6 +1481,9 @@ function completeWork(
1481
bubbleProperties(workInProgress);
1482
return null;
1483
case IncompleteClassComponent: {
1484
+ if (disableLegacyMode) {
1485
+ break;
1486
+ }
1487
// Same as class component case. I put it down here so that the tags are
1488
// sequential to ensure this switch is compiled to a jump table.
1489
const Component = workInProgress.type;
@@ -1740,7 +1749,11 @@ function completeWork(
1749
}
1750
}
1751
1743
- if (!nextIsHidden || (workInProgress.mode & ConcurrentMode) === NoMode) {
1752
+ if (
1753
+ !nextIsHidden ||
1754
+ (!disableLegacyMode &&
1755
+ (workInProgress.mode & ConcurrentMode) === NoMode)
1756
+ ) {
1757
bubbleProperties(workInProgress);
1758
} else {
1759
// Don't bubble properties for hidden children unless we're rendering
packages/react-reconciler/src/ReactFiberHooks.js
+2
-1
@@ -42,6 +42,7 @@ import {
42
debugRenderPhaseSideEffectsForStrictMode,
43
enableAsyncActions,
44
enableUseDeferredValueInitialArg,
45
+ disableLegacyMode,
46
} from 'shared/ReactFeatureFlags';
47
import {
48
REACT_CONTEXT_TYPE,
@@ -662,7 +663,7 @@ function finishRenderingHooks<Props, SecondArg>(
663
// need to mark fibers that commit in an incomplete state, somehow. For
664
// now I'll disable the warning that most of the bugs that would trigger
665
// it are either exclusive to concurrent mode or exist in both.
665
- (current.mode & ConcurrentMode) !== NoMode
666
+ (disableLegacyMode || (current.mode & ConcurrentMode) !== NoMode)
667
) {
668
console.error(
669
'Internal React error: Expected static flag was missing. Please ' +
packages/react-reconciler/src/ReactFiberRoot.js
+14
-8
@@ -33,6 +33,7 @@ import {
33
enableProfilerTimer,
34
enableUpdaterTracking,
35
enableTransitionTracing,
36
+ disableLegacyMode,
37
} from 'shared/ReactFeatureFlags';
38
import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue';
39
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
@@ -56,7 +57,7 @@ function FiberRootNode(
57
onRecoverableError: any,
58
formState: ReactFormState<any, any> | null,
59
) {
59
- this.tag = tag;
60
+ this.tag = disableLegacyMode ? ConcurrentRoot : tag;
61
this.containerInfo = containerInfo;
62
this.pendingChildren = null;
63
this.current = null;
@@ -123,13 +124,18 @@ function FiberRootNode(
124
}
125
126
if (__DEV__) {
126
- switch (tag) {
127
- case ConcurrentRoot:
128
- this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()';
129
- break;
130
- case LegacyRoot:
131
- this._debugRootType = hydrate ? 'hydrate()' : 'render()';
132
- break;
127
+ if (disableLegacyMode) {
128
+ // TODO: This varies by each renderer.
129
+ this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()';
130
+ } else {
131
+ switch (tag) {
132
+ case ConcurrentRoot:
133
+ this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()';
134
+ break;
135
+ case LegacyRoot:
136
+ this._debugRootType = hydrate ? 'hydrate()' : 'render()';
137
+ break;
138
+ }
139
}
140
}
141
}
packages/react-reconciler/src/ReactFiberRootScheduler.js
+9
-3
@@ -12,7 +12,10 @@ import type {Lane} from './ReactFiberLane';
12
import type {PriorityLevel} from 'scheduler/src/SchedulerPriorities';
13
import type {BatchConfigTransition} from './ReactFiberTracingMarkerComponent';
14
15
-import {enableDeferRootSchedulingToMicrotask} from 'shared/ReactFeatureFlags';
15
+import {
16
+ disableLegacyMode,
17
+ enableDeferRootSchedulingToMicrotask,
18
+} from 'shared/ReactFeatureFlags';
19
import {
20
NoLane,
21
NoLanes,
@@ -131,6 +134,7 @@ export function ensureRootIsScheduled(root: FiberRoot): void {
134
135
if (
136
__DEV__ &&
137
+ !disableLegacyMode &&
138
ReactCurrentActQueue.isBatchingLegacy &&
139
root.tag === LegacyRoot
140
) {
@@ -148,7 +152,9 @@ export function flushSyncWorkOnAllRoots() {
152
export function flushSyncWorkOnLegacyRootsOnly() {
153
// This is allowed to be called synchronously, but the caller should check
154
// the execution context first.
151
- flushSyncWorkAcrossRoots_impl(true);
155
+ if (!disableLegacyMode) {
156
+ flushSyncWorkAcrossRoots_impl(true);
157
+ }
158
}
159
160
function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
@@ -171,7 +177,7 @@ function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
177
didPerformSomeWork = false;
178
let root = firstScheduledRoot;
179
while (root !== null) {
174
- if (onlyLegacy && root.tag !== LegacyRoot) {
180
+ if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {
181
// Skip non-legacy roots.
182
} else {
183
const workInProgressRoot = getWorkInProgressRoot();
packages/react-reconciler/src/ReactFiberThrow.js
+18
-7
@@ -43,6 +43,7 @@ import {
43
enableLazyContextPropagation,
44
enableUpdaterTracking,
45
enablePostpone,
46
+ disableLegacyMode,
47
} from 'shared/ReactFeatureFlags';
48
import {createCapturedValueAtFiber} from './ReactCapturedValue';
49
import {
@@ -189,6 +190,7 @@ function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
190
// A legacy mode Suspense quirk, only relevant to hook components.
191
const tag = sourceFiber.tag;
192
if (
193
+ !disableLegacyMode &&
194
(sourceFiber.mode & ConcurrentMode) === NoMode &&
195
(tag === FunctionComponent ||
196
tag === ForwardRef ||
@@ -215,7 +217,10 @@ function markSuspenseBoundaryShouldCapture(
217
): Fiber | null {
218
// This marks a Suspense boundary so that when we're unwinding the stack,
219
// it captures the suspended "exception" and does a second (fallback) pass.
218
- if ((suspenseBoundary.mode & ConcurrentMode) === NoMode) {
220
+ if (
221
+ !disableLegacyMode &&
222
+ (suspenseBoundary.mode & ConcurrentMode) === NoMode
223
+ ) {
224
// Legacy Mode Suspense
225
//
226
// If the boundary is in legacy mode, we should *not*
@@ -354,7 +359,10 @@ function throwException(
359
resetSuspendedComponent(sourceFiber, rootRenderLanes);
360
361
if (__DEV__) {
357
- if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
362
+ if (
363
+ getIsHydrating() &&
364
+ (disableLegacyMode || sourceFiber.mode & ConcurrentMode)
365
+ ) {
366
markDidThrowWhileHydratingDEV();
367
}
368
}
@@ -383,7 +391,7 @@ function throwException(
391
// we don't have to recompute it on demand. This would also allow us
392
// to unify with `use` which needs to perform this logic even sooner,
393
// before `throwException` is called.
386
- if (sourceFiber.mode & ConcurrentMode) {
394
+ if (disableLegacyMode || sourceFiber.mode & ConcurrentMode) {
395
if (getShellBoundary() === null) {
396
// Suspended in the "shell" of the app. This is an undesirable
397
// loading state. We should avoid committing this tree.
@@ -451,14 +459,14 @@ function throwException(
459
// We only attach ping listeners in concurrent mode. Legacy
460
// Suspense always commits fallbacks synchronously, so there are
461
// no pings.
454
- if (suspenseBoundary.mode & ConcurrentMode) {
462
+ if (disableLegacyMode || suspenseBoundary.mode & ConcurrentMode) {
463
attachPingListener(root, wakeable, rootRenderLanes);
464
}
465
}
466
return false;
467
}
468
case OffscreenComponent: {
461
- if (suspenseBoundary.mode & ConcurrentMode) {
469
+ if (disableLegacyMode || suspenseBoundary.mode & ConcurrentMode) {
470
suspenseBoundary.flags |= ShouldCapture;
471
const isSuspenseyResource =
472
wakeable === noopSuspenseyCommitThenable;
@@ -497,7 +505,7 @@ function throwException(
505
// No boundary was found. Unless this is a sync update, this is OK.
506
// We can suspend and wait for more data to arrive.
507
500
- if (root.tag === ConcurrentRoot) {
508
+ if (disableLegacyMode || root.tag === ConcurrentRoot) {
509
// In a concurrent root, suspending without a Suspense boundary is
510
// allowed. It will suspend indefinitely without committing.
511
//
@@ -522,7 +530,10 @@ function throwException(
530
}
531
532
// This is a regular error, not a Suspense wakeable.
525
- if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
533
+ if (
534
+ getIsHydrating() &&
535
+ (disableLegacyMode || sourceFiber.mode & ConcurrentMode)
536
+ ) {
537
markDidThrowWhileHydratingDEV();
538
const suspenseBoundary = getSuspenseHandler();
539
// If the error was thrown during hydration, we may be able to recover by
packages/react-reconciler/src/ReactFiberWorkLoop.js
+17
-9
@@ -602,7 +602,7 @@ export function getCurrentTime(): number {
602
export function requestUpdateLane(fiber: Fiber): Lane {
603
// Special cases
604
const mode = fiber.mode;
605
- if ((mode & ConcurrentMode) === NoMode) {
605
+ if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
606
return (SyncLane: Lane);
607
} else if (
608
(executionContext & RenderContext) !== NoContext &&
@@ -669,7 +669,7 @@ function requestRetryLane(fiber: Fiber) {
669
670
// Special cases
671
const mode = fiber.mode;
672
- if ((mode & ConcurrentMode) === NoMode) {
672
+ if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
673
return (SyncLane: Lane);
674
}
675
@@ -824,6 +824,7 @@ export function scheduleUpdateOnFiber(
824
if (
825
lane === SyncLane &&
826
executionContext === NoContext &&
827
+ !disableLegacyMode &&
828
(fiber.mode & ConcurrentMode) === NoMode
829
) {
830
if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy) {
@@ -1367,7 +1368,10 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
1368
}
1369
1370
let exitStatus = renderRootSync(root, lanes);
1370
- if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
1371
+ if (
1372
+ (disableLegacyMode || root.tag !== LegacyRoot) &&
1373
+ exitStatus === RootErrored
1374
+ ) {
1375
// If something threw an error, try rendering one more time. We'll render
1376
// synchronously to block concurrent data mutations, and we'll includes
1377
// all pending updates are included. If it still fails after the second
@@ -1515,6 +1519,7 @@ export function flushSync<R>(fn: (() => R) | void): R | void {
1519
// next event, not at the end of the previous one.
1520
if (
1521
rootWithPendingPassiveEffects !== null &&
1522
+ !disableLegacyMode &&
1523
rootWithPendingPassiveEffects.tag === LegacyRoot &&
1524
(executionContext & (RenderContext | CommitContext)) === NoContext
1525
) {
@@ -3035,7 +3040,10 @@ function commitRootImpl(
3040
// TODO: We can optimize this by not scheduling the callback earlier. Since we
3041
// currently schedule the callback in multiple places, will wait until those
3042
// are consolidated.
3038
- if (includesSyncLane(pendingPassiveEffectsLanes) && root.tag !== LegacyRoot) {
3043
+ if (
3044
+ includesSyncLane(pendingPassiveEffectsLanes) &&
3045
+ (disableLegacyMode || root.tag !== LegacyRoot)
3046
+ ) {
3047
flushPassiveEffects();
3048
}
3049
@@ -3716,11 +3724,11 @@ function commitDoubleInvokeEffectsInDEV(
3724
hasPassiveEffects: boolean,
3725
) {
3726
if (__DEV__) {
3719
- if (useModernStrictMode && root.tag !== LegacyRoot) {
3727
+ if (useModernStrictMode && (disableLegacyMode || root.tag !== LegacyRoot)) {
3728
let doubleInvokeEffects = true;
3729
3730
if (
3723
- root.tag === ConcurrentRoot &&
3731
+ (disableLegacyMode || root.tag === ConcurrentRoot) &&
3732
!(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3733
) {
3734
doubleInvokeEffects = false;
@@ -3794,7 +3802,7 @@ export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) {
3802
return;
3803
}
3804
3797
- if (!(fiber.mode & ConcurrentMode)) {
3805
+ if (!disableLegacyMode && !(fiber.mode & ConcurrentMode)) {
3806
return;
3807
}
3808
@@ -3933,7 +3941,7 @@ function shouldForceFlushFallbacksInDEV() {
3941
3942
function warnIfUpdatesNotWrappedWithActDEV(fiber: Fiber): void {
3943
if (__DEV__) {
3936
- if (fiber.mode & ConcurrentMode) {
3944
+ if (disableLegacyMode || fiber.mode & ConcurrentMode) {
3945
if (!isConcurrentActEnvironment()) {
3946
// Not in an act environment. No need to warn.
3947
return;
@@ -3991,7 +3999,7 @@ function warnIfUpdatesNotWrappedWithActDEV(fiber: Fiber): void {
3999
function warnIfSuspenseResolutionNotWrappedWithActDEV(root: FiberRoot): void {
4000
if (__DEV__) {
4001
if (
3994
- root.tag !== LegacyRoot &&
4002
+ (disableLegacyMode || root.tag !== LegacyRoot) &&
4003
isConcurrentActEnvironment() &&
4004
ReactCurrentActQueue.current === null
4005
) {
packages/react-reconciler/src/__tests__/Activity-test.js
+1
-1
@@ -118,7 +118,7 @@ describe('Activity', () => {
118
);
119
});
120
121
- // @gate www
121
+ // @gate www && !disableLegacyMode
122
it('does not defer in legacy mode', async () => {
123
let setState;
124
function Foo() {
packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js
+2
-2
@@ -76,7 +76,7 @@ describe('DebugTracing', () => {
76
expect(logs).toEqual([]);
77
});
78
79
- // @gate experimental && build === 'development' && enableDebugTracing
79
+ // @gate experimental && build === 'development' && enableDebugTracing && !disableLegacyMode
80
it('should log sync render with suspense, legacy', async () => {
81
let resolveFakeSuspensePromise;
82
let didResolve = false;
@@ -116,7 +116,7 @@ describe('DebugTracing', () => {
116
expect(logs).toEqual(['log: ⚛️ Example resolved']);
117
});
118
119
- // @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
119
+ // @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense && !disableLegacyMode
120
it('should log sync render with CPU suspense, legacy', async () => {
121
function Example() {
122
console.log('<Example/>');
packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js
+1
-1
@@ -489,7 +489,7 @@ describe('ReactLazyContextPropagation', () => {
489
expect(root).toMatchRenderedOutput('BBB');
490
});
491
492
- // @gate enableLegacyCache
492
+ // @gate enableLegacyCache && !disableLegacyMode
493
test('context is propagated across retries (legacy)', async () => {
494
const root = ReactNoop.createLegacyRoot();
495
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+1
@@ -1622,6 +1622,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1622
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1623
});
1624
1625
+ // @gate !disableLegacyMode
1626
it(
1627
'in legacy mode, useEffect is deferred and updates finish synchronously ' +
1628
'(in a single batch)',
packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
+3
-3
@@ -108,7 +108,7 @@ describe('isomorphic act()', () => {
108
expect(returnValue).toEqual('hi');
109
});
110
111
- // @gate __DEV__
111
+ // @gate __DEV__ && !disableLegacyMode
112
test('in legacy mode, updates are batched', () => {
113
const root = ReactNoop.createLegacyRoot();
114
@@ -136,7 +136,7 @@ describe('isomorphic act()', () => {
136
expect(root).toMatchRenderedOutput('C');
137
});
138
139
- // @gate __DEV__
139
+ // @gate __DEV__ && !disableLegacyMode
140
test('in legacy mode, in an async scope, updates are batched until the first `await`', async () => {
141
const root = ReactNoop.createLegacyRoot();
142
@@ -167,7 +167,7 @@ describe('isomorphic act()', () => {
167
});
168
});
169
170
- // @gate __DEV__
170
+ // @gate __DEV__ && !disableLegacyMode
171
test('in legacy mode, in an async scope, updates are batched until the first `await` (regression test: batchedUpdates)', async () => {
172
const root = ReactNoop.createLegacyRoot();
173
packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
+1
-1
@@ -130,7 +130,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
130
131
const resolveText = resolveMostRecentTextCache;
132
133
- // @gate experimental || www
133
+ // @gate www && !disableLegacyMode
134
it('regression: false positive for legacy suspense', async () => {
135
const Child = ({text}) => {
136
// If text hasn't resolved, this will throw and exit before the passive
packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
+3
-3
@@ -324,7 +324,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
324
expect(ReactNoop).toMatchRenderedOutput(null);
325
});
326
327
- // @gate enableLegacyCache
327
+ // @gate enableLegacyCache && !disableLegacyMode
328
it('should not change behavior in sync', async () => {
329
class ClassText extends React.Component {
330
componentDidMount() {
@@ -445,7 +445,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
445
});
446
447
describe('layout effects within a tree that re-suspends in an update', () => {
448
- // @gate enableLegacyCache
448
+ // @gate enableLegacyCache && !disableLegacyMode
449
it('should not be destroyed or recreated in legacy roots', async () => {
450
function App({children = null}) {
451
Scheduler.log('App render');
@@ -2542,7 +2542,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2542
return null;
2543
}
2544
2545
- // @gate enableLegacyCache
2545
+ // @gate enableLegacyCache && !disableLegacyMode
2546
it('should not be cleared within legacy roots', async () => {
2547
class ClassComponent extends React.Component {
2548
render() {
packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
+12
-5
@@ -4,6 +4,7 @@ let ReactNoop;
4
let Scheduler;
5
let act;
6
let Random;
7
+let ReactFeatureFlags;
8
9
const SEED = process.env.FUZZ_TEST_SEED || 'default';
10
const prettyFormatPkg = require('pretty-format');
@@ -26,6 +27,7 @@ describe('ReactSuspenseFuzz', () => {
27
Scheduler = require('scheduler');
28
act = require('internal-test-utils').act;
29
Random = require('random-seed');
30
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
31
});
32
33
jest.setTimeout(20000);
@@ -163,16 +165,21 @@ describe('ReactSuspenseFuzz', () => {
165
resetCache();
166
167
// Do it again in legacy mode.
166
- const legacyRootThatSuspends = ReactNoop.createLegacyRoot();
167
- await act(() => {
168
- legacyRootThatSuspends.render(children);
169
- });
168
+ if (!ReactFeatureFlags.disableLegacyMode) {
169
+ const legacyRootThatSuspends = ReactNoop.createLegacyRoot();
170
+ await act(() => {
171
+ legacyRootThatSuspends.render(children);
172
+ });
173
+
174
+ expect(legacyRootThatSuspends.getChildrenAsJSX()).toEqual(
175
+ expectedOutput,
176
+ );
177
+ }
178
179
// Now compare the final output. It should be the same.
180
expect(concurrentRootThatSuspends.getChildrenAsJSX()).toEqual(
181
expectedOutput,
182
);
175
- expect(legacyRootThatSuspends.getChildrenAsJSX()).toEqual(expectedOutput);
183
184
// TODO: There are Scheduler logs in this test file but they were only
185
// added for debugging purposes; we don't make any assertions on them.
packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+1
-1
@@ -272,7 +272,7 @@ describe('ReactSuspenseList', () => {
272
);
273
});
274
275
- // @gate enableSuspenseList
275
+ // @gate enableSuspenseList && !disableLegacyMode
276
it('shows content independently in legacy mode regardless of option', async () => {
277
const A = createAsyncText('A');
278
const B = createAsyncText('B');
packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+2
@@ -296,6 +296,7 @@ describe('ReactSuspensePlaceholder', () => {
296
});
297
298
describe('when suspending during mount', () => {
299
+ // @gate !disableLegacyMode && !disableLegacyMode
300
it('properly accounts for base durations when a suspended times out in a legacy tree', async () => {
301
ReactNoop.renderLegacySyncRoot(<App shouldSuspend={true} />);
302
assertLog([
@@ -370,6 +371,7 @@ describe('ReactSuspensePlaceholder', () => {
371
});
372
373
describe('when suspending during update', () => {
374
+ // @gate !disableLegacyMode && !disableLegacyMode
375
it('properly accounts for base durations when a suspended times out in a legacy tree', async () => {
376
ReactNoop.renderLegacySyncRoot(
377
<App shouldSuspend={false} textRenderDuration={5} />,
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+13
-12
@@ -886,7 +886,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
886
}).not.toThrow();
887
});
888
889
- // @gate enableLegacyCache
889
+ // @gate enableLegacyCache && !disableLegacyMode
890
it('in legacy mode, errors when an update suspends without a Suspense boundary during a sync update', async () => {
891
const root = ReactNoop.createLegacyRoot();
892
await expect(async () => {
@@ -1032,7 +1032,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1032
});
1033
1034
describe('legacy mode mode', () => {
1035
- // @gate enableLegacyCache
1035
+ // @gate enableLegacyCache && !disableLegacyMode
1036
it('times out immediately', async () => {
1037
function App() {
1038
return (
@@ -1055,7 +1055,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1055
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result" />);
1056
});
1057
1058
- // @gate enableLegacyCache
1058
+ // @gate enableLegacyCache && !disableLegacyMode
1059
it('times out immediately when Suspense is in legacy mode', async () => {
1060
class UpdatingText extends React.Component {
1061
state = {step: 1};
@@ -1129,7 +1129,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1129
);
1130
});
1131
1132
- // @gate enableLegacyCache
1132
+ // @gate enableLegacyCache && !disableLegacyMode
1133
it('does not re-render siblings in loose mode', async () => {
1134
class TextWithLifecycle extends React.Component {
1135
componentDidMount() {
@@ -1204,7 +1204,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1204
);
1205
});
1206
1207
- // @gate enableLegacyCache
1207
+ // @gate enableLegacyCache && !disableLegacyMode
1208
it('suspends inside constructor', async () => {
1209
class AsyncTextInConstructor extends React.Component {
1210
constructor(props) {
@@ -1240,7 +1240,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1240
expect(ReactNoop).toMatchRenderedOutput(<span prop="Hi" />);
1241
});
1242
1243
- // @gate enableLegacyCache
1243
+ // @gate enableLegacyCache && !disableLegacyMode
1244
it('does not infinite loop if fallback contains lifecycle method', async () => {
1245
class Fallback extends React.Component {
1246
state = {
@@ -1283,7 +1283,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1283
});
1284
1285
if (global.__PERSISTENT__) {
1286
- // @gate enableLegacyCache
1286
+ // @gate enableLegacyCache && !disableLegacyMode
1287
it('hides/unhides suspended children before layout effects fire (persistent)', async () => {
1288
const {useRef, useLayoutEffect} = React;
1289
@@ -1327,7 +1327,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1327
assertLog(['Hi']);
1328
});
1329
} else {
1330
- // @gate enableLegacyCache
1330
+ // @gate enableLegacyCache && !disableLegacyMode
1331
it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
1332
const {useRef, useLayoutEffect} = React;
1333
@@ -1370,7 +1370,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1370
});
1371
}
1372
1373
- // @gate enableLegacyCache
1373
+ // @gate enableLegacyCache && !disableLegacyMode
1374
it('handles errors in the return path of a component that suspends', async () => {
1375
// Covers an edge case where an error is thrown inside the complete phase
1376
// of a component that is in the return path of a component that suspends.
@@ -1405,6 +1405,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1405
);
1406
});
1407
1408
+ // @gate !disableLegacyMode
1409
it('does not drop mounted effects', async () => {
1410
const never = {then() {}};
1411
@@ -1455,7 +1456,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1456
});
1457
});
1458
1458
- // @gate enableLegacyCache
1459
+ // @gate enableLegacyCache && !disableLegacyMode
1460
it('does not call lifecycles of a suspended component', async () => {
1461
class TextWithLifecycle extends React.Component {
1462
componentDidMount() {
@@ -1523,7 +1524,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1524
);
1525
});
1526
1526
- // @gate enableLegacyCache
1527
+ // @gate enableLegacyCache && !disableLegacyMode
1528
it('does not call lifecycles of a suspended component (hooks)', async () => {
1529
function TextWithLifecycle(props) {
1530
React.useLayoutEffect(() => {
@@ -3885,7 +3886,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3886
assertLog(['Unmount Child']);
3887
});
3888
3888
- // @gate enableLegacyCache
3889
+ // @gate enableLegacyCache && !disableLegacyMode
3890
it('should fire effect clean-up when deleting suspended tree (legacy)', async () => {
3891
const {useEffect} = React;
3892
packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js
+2
@@ -27,6 +27,7 @@ describe('StrictEffectsMode', () => {
27
ReactNoop = require('react-noop-renderer');
28
});
29
30
+ // @gate !disableLegacyMode
31
it('should not double invoke effects in legacy mode', async () => {
32
function App({text}) {
33
React.useEffect(() => {
@@ -430,6 +431,7 @@ describe('StrictEffectsMode', () => {
431
assertLog(['componentWillUnmount']);
432
});
433
434
+ // @gate !disableLegacyMode
435
it('should not double invoke class lifecycles in legacy mode', async () => {
436
class App extends React.PureComponent {
437
componentDidMount() {
packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
+2
@@ -34,6 +34,7 @@ describe('StrictEffectsMode defaults', () => {
34
assertLog = InternalTestUtils.assertLog;
35
});
36
37
+ // @gate !disableLegacyMode
38
it('should not double invoke effects in legacy mode', async () => {
39
function App({text}) {
40
React.useEffect(() => {
@@ -60,6 +61,7 @@ describe('StrictEffectsMode defaults', () => {
61
assertLog(['useLayoutEffect mount', 'useEffect mount']);
62
});
63
64
+ // @gate !disableLegacyMode
65
it('should not double invoke class lifecycles in legacy mode', async () => {
66
class App extends React.PureComponent {
67
componentDidMount() {
packages/react-reconciler/src/getComponentNameFromFiber.js
+9
-2
@@ -11,6 +11,7 @@ import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes';
11
import type {Fiber} from './ReactInternalTypes';
12
13
import {
14
+ disableLegacyMode,
15
enableLegacyHidden,
16
enableRenderableContext,
17
} from 'shared/ReactFeatureFlags';
@@ -35,6 +36,7 @@ import {
36
SimpleMemoComponent,
37
LazyComponent,
38
IncompleteClassComponent,
39
+ IncompleteFunctionComponent,
40
DehydratedFragment,
41
SuspenseListComponent,
42
ScopeComponent,
@@ -123,10 +125,15 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
125
return 'SuspenseList';
126
case TracingMarkerComponent:
127
return 'TracingMarker';
126
- // The display name for this tags come from the user-provided type:
128
+ // The display name for these tags come from the user-provided type:
129
+ case IncompleteClassComponent:
130
+ case IncompleteFunctionComponent:
131
+ if (disableLegacyMode) {
132
+ break;
133
+ }
134
+ // Fallthrough
135
case ClassComponent:
136
case FunctionComponent:
129
- case IncompleteClassComponent:
137
case MemoComponent:
138
case SimpleMemoComponent:
139
if (typeof type === 'function') {
packages/react/src/ReactAct.js
+20
-7
@@ -12,6 +12,8 @@ import type {RendererTask} from './ReactCurrentActQueue';
12
import ReactCurrentActQueue from './ReactCurrentActQueue';
13
import queueMacrotask from 'shared/enqueueTask';
14
15
+import {disableLegacyMode} from 'shared/ReactFeatureFlags';
16
+
17
// `act` calls can be nested, so we track the depth. This represents the
18
// number of `act` scopes on the stack.
19
let actScopeDepth = 0;
@@ -38,7 +40,9 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
40
// `act` calls can be nested.
41
//
42
// If we're already inside an `act` scope, reuse the existing queue.
41
- const prevIsBatchingLegacy = ReactCurrentActQueue.isBatchingLegacy;
43
+ const prevIsBatchingLegacy = !disableLegacyMode
44
+ ? ReactCurrentActQueue.isBatchingLegacy
45
+ : false;
46
const prevActQueue = ReactCurrentActQueue.current;
47
const prevActScopeDepth = actScopeDepth;
48
actScopeDepth++;
@@ -48,7 +52,9 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
52
// set to `true` while the given callback is executed, not for updates
53
// triggered during an async event, because this is how the legacy
54
// implementation of `act` behaved.
51
- ReactCurrentActQueue.isBatchingLegacy = true;
55
+ if (!disableLegacyMode) {
56
+ ReactCurrentActQueue.isBatchingLegacy = true;
57
+ }
58
59
let result;
60
// This tracks whether the `act` call is awaited. In certain cases, not
@@ -58,10 +64,13 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
64
// Reset this to `false` right before entering the React work loop. The
65
// only place we ever read this fields is just below, right after running
66
// the callback. So we don't need to reset after the callback runs.
61
- ReactCurrentActQueue.didScheduleLegacyUpdate = false;
67
+ if (!disableLegacyMode) {
68
+ ReactCurrentActQueue.didScheduleLegacyUpdate = false;
69
+ }
70
result = callback();
63
- const didScheduleLegacyUpdate =
64
- ReactCurrentActQueue.didScheduleLegacyUpdate;
71
+ const didScheduleLegacyUpdate = !disableLegacyMode
72
+ ? ReactCurrentActQueue.didScheduleLegacyUpdate
73
+ : false;
74
75
// Replicate behavior of original `act` implementation in legacy mode,
76
// which flushed updates immediately after the scope function exits, even
@@ -73,7 +82,9 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
82
// one used to track `act` scopes. Why, you may be wondering? Because
83
// that's how it worked before version 18. Yes, it's confusing! We should
84
// delete legacy mode!!
76
- ReactCurrentActQueue.isBatchingLegacy = prevIsBatchingLegacy;
85
+ if (!disableLegacyMode) {
86
+ ReactCurrentActQueue.isBatchingLegacy = prevIsBatchingLegacy;
87
+ }
88
} catch (error) {
89
// `isBatchingLegacy` gets reset using the regular stack, not the async
90
// one used to track `act` scopes. Why, you may be wondering? Because
@@ -82,7 +93,9 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
93
ReactCurrentActQueue.thrownErrors.push(error);
94
}
95
if (ReactCurrentActQueue.thrownErrors.length > 0) {
85
- ReactCurrentActQueue.isBatchingLegacy = prevIsBatchingLegacy;
96
+ if (!disableLegacyMode) {
97
+ ReactCurrentActQueue.isBatchingLegacy = prevIsBatchingLegacy;
98
+ }
99
popActScope(prevActQueue, prevActScopeDepth);
100
const thrownError = aggregateErrors(ReactCurrentActQueue.thrownErrors);
101
ReactCurrentActQueue.thrownErrors.length = 0;