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

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;