[DOM] disable legacy mode behind flag (#28468)
Adds a flag to disable legacy mode. Currently this flag is used to cause legacy mode apis like render and hydrate to throw. This change also removes render, hydrate, unmountComponentAtNode, and unstable_renderSubtreeIntoContainer from the experiemntal entrypoint. Right now for Meta builds this flag is off (legacy mode is still supported). In OSS builds this flag matches __NEXT_MAJOR__ which means it currently is on in experiemental. This means that after merging legacy mode is effectively removed from experimental builds. While this is a breaking change, experimental builds are not stable and users can pin to older versions or update their use of react-dom to no longer use legacy mode APIs.
Josh Story committed
Mar 4, 2024 at 08:19 UTC
1c02b9d2bdc18091cc6afec810fc1b361f00abdd
46 files changed
+483
-187
packages/react-devtools-shared/src/__tests__/store-test.js
+5
@@ -227,6 +227,7 @@ describe('Store', () => {
227
228
// @reactVersion >= 18.0
229
// @reactVersion < 19
230
+ // @gate !disableLegacyMode
231
it('should support mount and update operations for multiple roots (legacy render)', () => {
232
const Parent = ({count}) =>
233
new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -941,6 +942,7 @@ describe('Store', () => {
942
943
// @reactVersion >= 18.0
944
// @reactVersion < 19
945
+ // @gate !disableLegacyMode
946
it('should support mount and update operations for multiple roots (legacy render)', () => {
947
const Parent = ({count}) =>
948
new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -1469,6 +1471,7 @@ describe('Store', () => {
1471
1472
// @reactVersion >= 18.0
1473
// @reactVersion < 19
1474
+ // @gate !disableLegacyMode
1475
it('detects and updates profiling support based on the attached roots (legacy render)', () => {
1476
const Component = () => null;
1477
@@ -1632,6 +1635,7 @@ describe('Store', () => {
1635
1636
// @reactVersion >= 18.0
1637
// @reactVersion < 19
1638
+ // @gate !disableLegacyMode
1639
it('should support Lazy components (legacy render)', async () => {
1640
const container = document.createElement('div');
1641
@@ -1702,6 +1706,7 @@ describe('Store', () => {
1706
1707
// @reactVersion >= 18.0
1708
// @reactVersion < 19
1709
+ // @gate !disableLegacyMode
1710
it('should support Lazy components that are unmounted before they finish loading (legacy render)', async () => {
1711
const container = document.createElement('div');
1712
packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
+1
@@ -36,6 +36,7 @@ describe('StoreStress (Legacy Mode)', () => {
36
// It renders different trees that should produce the same output.
37
// @reactVersion >= 16.9
38
// @reactVersion < 19
39
+ // @gate !disableLegacyMode
40
it('should handle a stress test with different tree operations (Legacy Mode)', () => {
41
let setShowX;
42
const A = () => 'a';
packages/react-dom/index.experimental.js
-4
@@ -14,11 +14,7 @@ export {
14
hydrateRoot,
15
findDOMNode,
16
flushSync,
17
- hydrate,
18
- render,
19
- unmountComponentAtNode,
17
unstable_batchedUpdates,
21
- unstable_renderSubtreeIntoContainer,
18
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
19
useFormStatus,
20
useFormState,
packages/react-dom/src/__tests__/ReactComponent-test.js
+2
@@ -26,6 +26,7 @@ describe('ReactComponent', () => {
26
act = require('internal-test-utils').act;
27
});
28
29
+ // @gate !disableLegacyMode
30
it('should throw on invalid render targets in legacy roots', () => {
31
const container = document.createElement('div');
32
// jQuery objects are basically arrays; people often pass them in by mistake
@@ -455,6 +456,7 @@ describe('ReactComponent', () => {
456
/* eslint-enable indent */
457
});
458
459
+ // @gate !disableLegacyMode
460
it('fires the callback after a component is rendered in legacy roots', () => {
461
const callback = jest.fn();
462
const container = document.createElement('div');
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+1
@@ -630,6 +630,7 @@ describe('ReactCompositeComponent-state', () => {
630
);
631
});
632
633
+ // @gate !disableLegacyMode
634
it('Legacy mode should support setState in componentWillUnmount (#18851)', () => {
635
let subscription;
636
class A extends React.Component {
packages/react-dom/src/__tests__/ReactDOM-test.js
+2
@@ -164,6 +164,7 @@ describe('ReactDOM', () => {
164
expect(dog.className).toBe('bigdog');
165
});
166
167
+ // @gate !disableLegacyMode
168
it('throws in render() if the mount callback in legacy roots is not a function', async () => {
169
function Foo() {
170
this.a = 1;
@@ -216,6 +217,7 @@ describe('ReactDOM', () => {
217
);
218
});
219
220
+ // @gate !disableLegacyMode
221
it('throws in render() if the update callback in legacy roots is not a function', async () => {
222
function Foo() {
223
this.a = 1;
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+13
-9
@@ -332,7 +332,7 @@ describe('ReactDOMComponent', () => {
332
});
333
});
334
335
- it('throws with Temporal-like objects as style values', () => {
335
+ it('throws with Temporal-like objects as style values', async () => {
336
class TemporalLike {
337
valueOf() {
338
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -344,14 +344,17 @@ describe('ReactDOMComponent', () => {
344
}
345
}
346
const style = {fontSize: new TemporalLike()};
347
- const div = document.createElement('div');
348
- const test = () => ReactDOM.render(<span style={style} />, div);
349
- expect(() =>
350
- expect(test).toThrowError(new TypeError('prod message')),
351
- ).toErrorDev(
352
- 'Warning: The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
353
- ' This value must be coerced to a string before using it here.',
354
- );
347
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
348
+ await expect(async () => {
349
+ await expect(async () => {
350
+ await act(() => {
351
+ root.render(<span style={style} />);
352
+ });
353
+ }).toErrorDev(
354
+ 'Warning: The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
355
+ ' This value must be coerced to a string before using it here.',
356
+ );
357
+ }).rejects.toThrowError(new TypeError('prod message'));
358
});
359
360
it('should update styles if initially null', async () => {
@@ -3688,6 +3691,7 @@ describe('ReactDOMComponent', () => {
3691
expect(typeof portalContainer.onclick).toBe('function');
3692
});
3693
3694
+ // @gate !disableLegacyMode
3695
it('does not add onclick handler to the React root in legacy mode', () => {
3696
const container = document.createElement('div');
3697
packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js
+7
@@ -55,6 +55,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
55
});
56
57
describe('ReactDOM.render', () => {
58
+ // @gate !disableLegacyMode
59
it('logs errors during event handlers', async () => {
60
spyOnDevAndProd(console, 'error');
61
@@ -156,6 +157,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
157
}
158
});
159
160
+ // @gate !disableLegacyMode
161
it('logs render errors without an error boundary', async () => {
162
spyOnDevAndProd(console, 'error');
163
@@ -223,6 +225,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
225
}
226
});
227
228
+ // @gate !disableLegacyMode
229
it('logs render errors with an error boundary', async () => {
230
spyOnDevAndProd(console, 'error');
231
@@ -295,6 +298,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
298
}
299
});
300
301
+ // @gate !disableLegacyMode
302
it('logs layout effect errors without an error boundary', async () => {
303
spyOnDevAndProd(console, 'error');
304
@@ -365,6 +369,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
369
}
370
});
371
372
+ // @gate !disableLegacyMode
373
it('logs layout effect errors with an error boundary', async () => {
374
spyOnDevAndProd(console, 'error');
375
@@ -440,6 +445,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
445
}
446
});
447
448
+ // @gate !disableLegacyMode
449
it('logs passive effect errors without an error boundary', async () => {
450
spyOnDevAndProd(console, 'error');
451
@@ -511,6 +517,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
517
}
518
});
519
520
+ // @gate !disableLegacyMode
521
it('logs passive effect errors with an error boundary', async () => {
522
spyOnDevAndProd(console, 'error');
523
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+1
@@ -50,6 +50,7 @@ describe('ReactDOMFiberAsync', () => {
50
document.body.removeChild(container);
51
});
52
53
+ // @gate !disableLegacyMode
54
it('renders synchronously by default in legacy mode', () => {
55
const ops = [];
56
ReactDOM.render(<div>Hi</div>, container, () => {
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+45
@@ -35,6 +35,7 @@ describe('ReactDOMHooks', () => {
35
document.body.removeChild(container);
36
});
37
38
+ // @gate !disableLegacyMode
39
it('can ReactDOM.render() from useEffect', async () => {
40
const container2 = document.createElement('div');
41
const container3 = document.createElement('div');
@@ -76,6 +77,50 @@ describe('ReactDOMHooks', () => {
77
expect(container3.textContent).toBe('6');
78
});
79
80
+ it('can render() from useEffect', async () => {
81
+ const container2 = document.createElement('div');
82
+ const container3 = document.createElement('div');
83
+
84
+ const root1 = ReactDOMClient.createRoot(container);
85
+ const root2 = ReactDOMClient.createRoot(container2);
86
+ const root3 = ReactDOMClient.createRoot(container3);
87
+
88
+ function Example1({n}) {
89
+ React.useEffect(() => {
90
+ root2.render(<Example2 n={n} />);
91
+ });
92
+ return 1 * n;
93
+ }
94
+
95
+ function Example2({n}) {
96
+ React.useEffect(() => {
97
+ root3.render(<Example3 n={n} />);
98
+ });
99
+ return 2 * n;
100
+ }
101
+
102
+ function Example3({n}) {
103
+ return 3 * n;
104
+ }
105
+
106
+ await act(() => {
107
+ root1.render(<Example1 n={1} />);
108
+ });
109
+ await waitForAll([]);
110
+ expect(container.textContent).toBe('1');
111
+ expect(container2.textContent).toBe('2');
112
+ expect(container3.textContent).toBe('3');
113
+
114
+ await act(() => {
115
+ root1.render(<Example1 n={2} />);
116
+ });
117
+ await waitForAll([]);
118
+ expect(container.textContent).toBe('2');
119
+ expect(container2.textContent).toBe('4');
120
+ expect(container3.textContent).toBe('6');
121
+ });
122
+
123
+ // @gate !disableLegacyMode
124
it('should not bail out when an update is scheduled from within an event handler', () => {
125
const {createRef, useCallback, useState} = React;
126
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+11
-13
@@ -733,7 +733,7 @@ describe('ReactDOMInput', () => {
733
expect(node.value).toBe('foobar');
734
});
735
736
- it('should throw for date inputs if `defaultValue` is an object where valueOf() throws', () => {
736
+ it('should throw for date inputs if `defaultValue` is an object where valueOf() throws', async () => {
737
class TemporalLike {
738
valueOf() {
739
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -744,19 +744,16 @@ describe('ReactDOMInput', () => {
744
return '2020-01-01';
745
}
746
}
747
- const legacyContainer = document.createElement('div');
748
- document.body.appendChild(legacyContainer);
749
- const test = () =>
750
- ReactDOM.render(
751
- <input defaultValue={new TemporalLike()} type="date" />,
752
- legacyContainer,
747
+ await expect(async () => {
748
+ await expect(async () => {
749
+ await act(() => {
750
+ root.render(<input defaultValue={new TemporalLike()} type="date" />);
751
+ });
752
+ }).toErrorDev(
753
+ 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
754
+ 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
755
);
754
- expect(() =>
755
- expect(test).toThrowError(new TypeError('prod message')),
756
- ).toErrorDev(
757
- 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
758
- 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
759
- );
756
+ }).rejects.toThrowError(new TypeError('prod message'));
757
});
758
759
it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
@@ -1736,6 +1733,7 @@ describe('ReactDOMInput', () => {
1733
assertInputTrackingIsCurrent(container);
1734
});
1735
1736
+ // @gate !disableLegacyMode
1737
it('should control radio buttons if the tree updates during render in legacy mode', async () => {
1738
container.remove();
1739
container = document.createElement('div');
packages/react-dom/src/__tests__/ReactDOMLegacyComponentTree-test.internal.js
+2
@@ -27,6 +27,7 @@ describe('ReactDOMComponentTree', () => {
27
container = null;
28
});
29
30
+ // @gate !disableLegacyMode
31
it('finds instance of node that is attempted to be unmounted', () => {
32
const component = <div />;
33
const node = ReactDOM.render(<div>{component}</div>, container);
@@ -39,6 +40,7 @@ describe('ReactDOMComponentTree', () => {
40
);
41
});
42
43
+ // @gate !disableLegacyMode
44
it('finds instance from node to stop rendering over other react rendered components', () => {
45
const component = (
46
<div>
packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js
+42
@@ -27,6 +27,7 @@ describe('ReactDOMLegacyFiber', () => {
27
jest.restoreAllMocks();
28
});
29
30
+ // @gate !disableLegacyMode
31
it('should render strings as children', () => {
32
const Box = ({value}) => <div>{value}</div>;
33
@@ -34,6 +35,7 @@ describe('ReactDOMLegacyFiber', () => {
35
expect(container.textContent).toEqual('foo');
36
});
37
38
+ // @gate !disableLegacyMode
39
it('should render numbers as children', () => {
40
const Box = ({value}) => <div>{value}</div>;
41
@@ -42,6 +44,7 @@ describe('ReactDOMLegacyFiber', () => {
44
expect(container.textContent).toEqual('10');
45
});
46
47
+ // @gate !disableLegacyMode
48
it('should be called a callback argument', () => {
49
// mounting phase
50
let called = false;
@@ -54,6 +57,7 @@ describe('ReactDOMLegacyFiber', () => {
57
expect(called).toEqual(true);
58
});
59
60
+ // @gate !disableLegacyMode
61
it('should call a callback argument when the same element is re-rendered', () => {
62
class Foo extends React.Component {
63
render() {
@@ -75,6 +79,7 @@ describe('ReactDOMLegacyFiber', () => {
79
expect(called).toEqual(true);
80
});
81
82
+ // @gate !disableLegacyMode
83
it('should render a component returning strings directly from render', () => {
84
const Text = ({value}) => value;
85
@@ -82,6 +87,7 @@ describe('ReactDOMLegacyFiber', () => {
87
expect(container.textContent).toEqual('foo');
88
});
89
90
+ // @gate !disableLegacyMode
91
it('should render a component returning numbers directly from render', () => {
92
const Text = ({value}) => value;
93
@@ -90,6 +96,7 @@ describe('ReactDOMLegacyFiber', () => {
96
expect(container.textContent).toEqual('10');
97
});
98
99
+ // @gate !disableLegacyMode
100
it('finds the DOM Text node of a string child', () => {
101
class Text extends React.Component {
102
render() {
@@ -109,6 +116,7 @@ describe('ReactDOMLegacyFiber', () => {
116
expect(textNode.nodeValue).toBe('foo');
117
});
118
119
+ // @gate !disableLegacyMode
120
it('finds the first child when a component returns a fragment', () => {
121
class Fragment extends React.Component {
122
render() {
@@ -126,6 +134,7 @@ describe('ReactDOMLegacyFiber', () => {
134
expect(firstNode.tagName).toBe('DIV');
135
});
136
137
+ // @gate !disableLegacyMode
138
it('finds the first child even when fragment is nested', () => {
139
class Wrapper extends React.Component {
140
render() {
@@ -154,6 +163,7 @@ describe('ReactDOMLegacyFiber', () => {
163
expect(firstNode.tagName).toBe('DIV');
164
});
165
166
+ // @gate !disableLegacyMode
167
it('finds the first child even when first child renders null', () => {
168
class NullComponent extends React.Component {
169
render() {
@@ -177,6 +187,7 @@ describe('ReactDOMLegacyFiber', () => {
187
expect(firstNode.tagName).toBe('DIV');
188
});
189
190
+ // @gate !disableLegacyMode
191
it('renders an empty fragment', () => {
192
const Div = () => <div />;
193
const EmptyFragment = () => <></>;
@@ -232,6 +243,7 @@ describe('ReactDOMLegacyFiber', () => {
243
expect(testContainer.innerHTML).toBe('');
244
};
245
246
+ // @gate !disableLegacyMode
247
it('should render one portal', () => {
248
const portalContainer = document.createElement('div');
249
@@ -247,6 +259,7 @@ describe('ReactDOMLegacyFiber', () => {
259
expect(container.innerHTML).toBe('');
260
});
261
262
+ // @gate !disableLegacyMode
263
it('should render many portals', () => {
264
const portalContainer1 = document.createElement('div');
265
const portalContainer2 = document.createElement('div');
@@ -347,6 +360,7 @@ describe('ReactDOMLegacyFiber', () => {
360
]);
361
});
362
363
+ // @gate !disableLegacyMode
364
it('should render nested portals', () => {
365
const portalContainer1 = document.createElement('div');
366
const portalContainer2 = document.createElement('div');
@@ -390,6 +404,7 @@ describe('ReactDOMLegacyFiber', () => {
404
expect(container.innerHTML).toBe('');
405
});
406
407
+ // @gate !disableLegacyMode
408
it('should reconcile portal children', () => {
409
const portalContainer = document.createElement('div');
410
@@ -436,6 +451,7 @@ describe('ReactDOMLegacyFiber', () => {
451
expect(container.innerHTML).toBe('<div></div>');
452
});
453
454
+ // @gate !disableLegacyMode
455
it('should unmount empty portal component wherever it appears', () => {
456
const portalContainer = document.createElement('div');
457
@@ -470,6 +486,7 @@ describe('ReactDOMLegacyFiber', () => {
486
expect(container.innerHTML).toBe('<div><div>parent</div></div>');
487
});
488
489
+ // @gate !disableLegacyMode
490
it('should keep track of namespace across portals (simple)', () => {
491
assertNamespacesMatch(
492
<svg {...expectSVG}>
@@ -498,6 +515,7 @@ describe('ReactDOMLegacyFiber', () => {
515
);
516
});
517
518
+ // @gate !disableLegacyMode
519
it('should keep track of namespace across portals (medium)', () => {
520
assertNamespacesMatch(
521
<svg {...expectSVG}>
@@ -562,6 +580,7 @@ describe('ReactDOMLegacyFiber', () => {
580
);
581
});
582
583
+ // @gate !disableLegacyMode
584
it('should keep track of namespace across portals (complex)', () => {
585
assertNamespacesMatch(
586
<div {...expectHTML}>
@@ -636,6 +655,7 @@ describe('ReactDOMLegacyFiber', () => {
655
);
656
});
657
658
+ // @gate !disableLegacyMode
659
it('should unwind namespaces on uncaught errors', () => {
660
function BrokenRender() {
661
throw new Error('Hello');
@@ -651,6 +671,7 @@ describe('ReactDOMLegacyFiber', () => {
671
assertNamespacesMatch(<div {...expectHTML} />);
672
});
673
674
+ // @gate !disableLegacyMode
675
it('should unwind namespaces on caught errors', () => {
676
function BrokenRender() {
677
throw new Error('Hello');
@@ -684,6 +705,7 @@ describe('ReactDOMLegacyFiber', () => {
705
assertNamespacesMatch(<div {...expectHTML} />);
706
});
707
708
+ // @gate !disableLegacyMode
709
it('should unwind namespaces on caught errors in a portal', () => {
710
function BrokenRender() {
711
throw new Error('Hello');
@@ -719,6 +741,7 @@ describe('ReactDOMLegacyFiber', () => {
741
});
742
743
// @gate !disableLegacyContext
744
+ // @gate !disableLegacyMode
745
it('should pass portal context when rendering subtree elsewhere', () => {
746
const portalContainer = document.createElement('div');
747
@@ -754,6 +777,7 @@ describe('ReactDOMLegacyFiber', () => {
777
});
778
779
// @gate !disableLegacyContext
780
+ // @gate !disableLegacyMode
781
it('should update portal context if it changes due to setState', () => {
782
const portalContainer = document.createElement('div');
783
@@ -799,6 +823,7 @@ describe('ReactDOMLegacyFiber', () => {
823
});
824
825
// @gate !disableLegacyContext
826
+ // @gate !disableLegacyMode
827
it('should update portal context if it changes due to re-render', () => {
828
const portalContainer = document.createElement('div');
829
@@ -839,6 +864,7 @@ describe('ReactDOMLegacyFiber', () => {
864
expect(container.innerHTML).toBe('');
865
});
866
867
+ // @gate !disableLegacyMode
868
it('findDOMNode should find dom element after expanding a fragment', () => {
869
class MyNode extends React.Component {
870
render() {
@@ -859,6 +885,7 @@ describe('ReactDOMLegacyFiber', () => {
885
expect(b.tagName).toBe('SPAN');
886
});
887
888
+ // @gate !disableLegacyMode
889
it('should bubble events from the portal to the parent', () => {
890
const portalContainer = document.createElement('div');
891
document.body.appendChild(portalContainer);
@@ -890,6 +917,7 @@ describe('ReactDOMLegacyFiber', () => {
917
}
918
});
919
920
+ // @gate !disableLegacyMode
921
it('should not onMouseLeave when staying in the portal', () => {
922
const portalContainer = document.createElement('div');
923
document.body.appendChild(portalContainer);
@@ -966,6 +994,7 @@ describe('ReactDOMLegacyFiber', () => {
994
});
995
996
// Regression test for https://github.com/facebook/react/issues/19562
997
+ // @gate !disableLegacyMode
998
it('does not fire mouseEnter twice when relatedTarget is the root node', () => {
999
let ops = [];
1000
let target = null;
@@ -1016,6 +1045,7 @@ describe('ReactDOMLegacyFiber', () => {
1045
expect(ops).toEqual([]);
1046
});
1047
1048
+ // @gate !disableLegacyMode
1049
it('listens to events that do not exist in the Portal subtree', () => {
1050
const onClick = jest.fn();
1051
@@ -1043,6 +1073,7 @@ describe('ReactDOMLegacyFiber', () => {
1073
}).toThrow('Target container is not a DOM element.');
1074
});
1075
1076
+ // @gate !disableLegacyMode
1077
it('should warn for non-functional event listeners', () => {
1078
class Example extends React.Component {
1079
render() {
@@ -1056,6 +1087,7 @@ describe('ReactDOMLegacyFiber', () => {
1087
);
1088
});
1089
1090
+ // @gate !disableLegacyMode
1091
it('should warn with a special message for `false` event listeners', () => {
1092
class Example extends React.Component {
1093
render() {
@@ -1071,6 +1103,7 @@ describe('ReactDOMLegacyFiber', () => {
1103
);
1104
});
1105
1106
+ // @gate !disableLegacyMode
1107
it('should not update event handlers until commit', () => {
1108
spyOnDev(console, 'error');
1109
@@ -1168,6 +1201,7 @@ describe('ReactDOMLegacyFiber', () => {
1201
}
1202
});
1203
1204
+ // @gate !disableLegacyMode
1205
it('should not crash encountering low-priority tree', () => {
1206
ReactDOM.render(
1207
<div hidden={true}>
@@ -1177,6 +1211,7 @@ describe('ReactDOMLegacyFiber', () => {
1211
);
1212
});
1213
1214
+ // @gate !disableLegacyMode
1215
it('should not warn when rendering into an empty container', () => {
1216
ReactDOM.render(<div>foo</div>, container);
1217
expect(container.innerHTML).toBe('<div>foo</div>');
@@ -1186,6 +1221,7 @@ describe('ReactDOMLegacyFiber', () => {
1221
expect(container.innerHTML).toBe('<div>bar</div>');
1222
});
1223
1224
+ // @gate !disableLegacyMode
1225
it('should warn when replacing a container which was manually updated outside of React', () => {
1226
// when not messing with the DOM outside of React
1227
ReactDOM.render(<div key="1">foo</div>, container);
@@ -1210,6 +1246,7 @@ describe('ReactDOMLegacyFiber', () => {
1246
}).toThrowError();
1247
});
1248
1249
+ // @gate !disableLegacyMode
1250
it('should warn when doing an update to a container manually updated outside of React', () => {
1251
// when not messing with the DOM outside of React
1252
ReactDOM.render(<div>foo</div>, container);
@@ -1227,6 +1264,7 @@ describe('ReactDOMLegacyFiber', () => {
1264
);
1265
});
1266
1267
+ // @gate !disableLegacyMode
1268
it('should warn when doing an update to a container manually cleared outside of React', () => {
1269
// when not messing with the DOM outside of React
1270
ReactDOM.render(<div>foo</div>, container);
@@ -1244,6 +1282,7 @@ describe('ReactDOMLegacyFiber', () => {
1282
);
1283
});
1284
1285
+ // @gate !disableLegacyMode
1286
it('should render a text component with a text DOM node on the same document as the container', () => {
1287
// 1. Create a new document through the use of iframe
1288
// 2. Set up the spy to make asserts when a text component
@@ -1274,6 +1313,7 @@ describe('ReactDOMLegacyFiber', () => {
1313
expect(iframeContainer.appendChild).toHaveBeenCalledTimes(1);
1314
});
1315
1316
+ // @gate !disableLegacyMode
1317
it('should mount into a document fragment', () => {
1318
const fragment = document.createDocumentFragment();
1319
ReactDOM.render(<div>foo</div>, fragment);
@@ -1283,6 +1323,7 @@ describe('ReactDOMLegacyFiber', () => {
1323
});
1324
1325
// Regression test for https://github.com/facebook/react/issues/12643#issuecomment-413727104
1326
+ // @gate !disableLegacyMode
1327
it('should not diff memoized host components', () => {
1328
const inputRef = React.createRef();
1329
let didCallOnChange = false;
@@ -1341,6 +1382,7 @@ describe('ReactDOMLegacyFiber', () => {
1382
expect(didCallOnChange).toBe(true);
1383
});
1384
1385
+ // @gate !disableLegacyMode
1386
it('unmounted legacy roots should never clear newer root content from a container', () => {
1387
const ref = React.createRef();
1388
packages/react-dom/src/__tests__/ReactDOMOption-test.js
+62
-63
@@ -13,7 +13,6 @@ describe('ReactDOMOption', () => {
13
let React;
14
let ReactDOMClient;
15
let ReactDOMServer;
16
- let ReactTestUtils;
16
let act;
17
18
beforeEach(() => {
@@ -21,41 +20,47 @@ describe('ReactDOMOption', () => {
20
React = require('react');
21
ReactDOMClient = require('react-dom/client');
22
ReactDOMServer = require('react-dom/server');
24
- ReactTestUtils = require('react-dom/test-utils');
23
act = require('internal-test-utils').act;
24
});
25
28
- it('should flatten children to a string', () => {
26
+ async function renderIntoDocument(children) {
27
+ const container = document.createElement('div');
28
+ const root = ReactDOMClient.createRoot(container);
29
+ await act(async () => root.render(children));
30
+ return container;
31
+ }
32
+
33
+ it('should flatten children to a string', async () => {
34
const stub = (
35
<option>
36
{1} {'foo'}
37
</option>
38
);
34
- const node = ReactTestUtils.renderIntoDocument(stub);
39
+ const container = await renderIntoDocument(stub);
40
36
- expect(node.innerHTML).toBe('1 foo');
41
+ expect(container.firstChild.innerHTML).toBe('1 foo');
42
});
43
39
- it('should warn for invalid child tags', () => {
44
+ it('should warn for invalid child tags', async () => {
45
const el = (
46
<option value="12">
47
{1} <div /> {2}
48
</option>
49
);
45
- let node;
46
- expect(() => {
47
- node = ReactTestUtils.renderIntoDocument(el);
50
+ let container;
51
+ await expect(async () => {
52
+ container = await renderIntoDocument(el);
53
}).toErrorDev(
54
'In HTML, <div> cannot be a child of <option>.\n' +
55
'This will cause a hydration error.\n' +
56
' in div (at **)\n' +
57
' in option (at **)',
58
);
54
- expect(node.innerHTML).toBe('1 <div></div> 2');
55
- ReactTestUtils.renderIntoDocument(el);
59
+ expect(container.firstChild.innerHTML).toBe('1 <div></div> 2');
60
+ await renderIntoDocument(el);
61
});
62
58
- it('should warn for component child if no value prop is provided', () => {
63
+ it('should warn for component child if no value prop is provided', async () => {
64
function Foo() {
65
return '2';
66
}
@@ -64,18 +69,18 @@ describe('ReactDOMOption', () => {
69
{1} <Foo /> {3}
70
</option>
71
);
67
- let node;
68
- expect(() => {
69
- node = ReactTestUtils.renderIntoDocument(el);
72
+ let container;
73
+ await expect(async () => {
74
+ container = await renderIntoDocument(el);
75
}).toErrorDev(
76
'Cannot infer the option value of complex children. ' +
77
'Pass a `value` prop or use a plain string as children to <option>.',
78
);
74
- expect(node.innerHTML).toBe('1 2 3');
75
- ReactTestUtils.renderIntoDocument(el);
79
+ expect(container.firstChild.innerHTML).toBe('1 2 3');
80
+ await renderIntoDocument(el);
81
});
82
78
- it('should not warn for component child if value prop is provided', () => {
83
+ it('should not warn for component child if value prop is provided', async () => {
84
function Foo() {
85
return '2';
86
}
@@ -84,12 +89,12 @@ describe('ReactDOMOption', () => {
89
{1} <Foo /> {3}
90
</option>
91
);
87
- const node = ReactTestUtils.renderIntoDocument(el);
88
- expect(node.innerHTML).toBe('1 2 3');
89
- ReactTestUtils.renderIntoDocument(el);
92
+ const container = await renderIntoDocument(el);
93
+ expect(container.firstChild.innerHTML).toBe('1 2 3');
94
+ await renderIntoDocument(el);
95
});
96
92
- it('should ignore null/undefined/false children without warning', () => {
97
+ it('should ignore null/undefined/false children without warning', async () => {
98
const stub = (
99
<option>
100
{1} {false}
@@ -98,38 +103,38 @@ describe('ReactDOMOption', () => {
103
{undefined} {2}
104
</option>
105
);
101
- const node = ReactTestUtils.renderIntoDocument(stub);
106
+ const container = await renderIntoDocument(stub);
107
103
- expect(node.innerHTML).toBe('1 2');
108
+ expect(container.firstChild.innerHTML).toBe('1 2');
109
});
110
106
- it('should throw on object children', () => {
107
- expect(() => {
108
- ReactTestUtils.renderIntoDocument(<option>{{}}</option>);
109
- }).toThrow('Objects are not valid as a React child');
110
- expect(() => {
111
- ReactTestUtils.renderIntoDocument(<option>{[{}]}</option>);
112
- }).toThrow('Objects are not valid as a React child');
113
- expect(() => {
114
- ReactTestUtils.renderIntoDocument(
111
+ it('should throw on object children', async () => {
112
+ await expect(async () =>
113
+ renderIntoDocument(<option>{{}}</option>),
114
+ ).rejects.toThrow('Objects are not valid as a React child');
115
+ await expect(async () => {
116
+ await renderIntoDocument(<option>{[{}]}</option>);
117
+ }).rejects.toThrow('Objects are not valid as a React child');
118
+ await expect(async () => {
119
+ await renderIntoDocument(
120
<option>
121
{{}}
122
<span />
123
</option>,
124
);
120
- }).toThrow('Objects are not valid as a React child');
121
- expect(() => {
122
- ReactTestUtils.renderIntoDocument(
125
+ }).rejects.toThrow('Objects are not valid as a React child');
126
+ await expect(async () => {
127
+ await renderIntoDocument(
128
<option>
129
{'1'}
130
{{}}
131
{2}
132
</option>,
133
);
129
- }).toThrow('Objects are not valid as a React child');
134
+ }).rejects.toThrow('Objects are not valid as a React child');
135
});
136
132
- it('should support element-ish child', () => {
137
+ it('should support element-ish child', async () => {
138
// This is similar to <fbt>.
139
// We don't toString it because you must instead provide a value prop.
140
const obj = {
@@ -145,51 +150,45 @@ describe('ReactDOMOption', () => {
150
},
151
};
152
148
- let node = ReactTestUtils.renderIntoDocument(
149
- <option value="a">{obj}</option>,
150
- );
151
- expect(node.innerHTML).toBe('hello');
153
+ let container = await renderIntoDocument(<option value="a">{obj}</option>);
154
+ expect(container.firstChild.innerHTML).toBe('hello');
155
153
- node = ReactTestUtils.renderIntoDocument(
154
- <option value="b">{[obj]}</option>,
155
- );
156
- expect(node.innerHTML).toBe('hello');
156
+ container = await renderIntoDocument(<option value="b">{[obj]}</option>);
157
+ expect(container.firstChild.innerHTML).toBe('hello');
158
158
- node = ReactTestUtils.renderIntoDocument(
159
- <option value={obj}>{obj}</option>,
160
- );
161
- expect(node.innerHTML).toBe('hello');
162
- expect(node.value).toBe('hello');
159
+ container = await renderIntoDocument(<option value={obj}>{obj}</option>);
160
+ expect(container.firstChild.innerHTML).toBe('hello');
161
+ expect(container.firstChild.value).toBe('hello');
162
164
- node = ReactTestUtils.renderIntoDocument(
163
+ container = await renderIntoDocument(
164
<option value={obj}>
165
{'1'}
166
{obj}
167
{2}
168
</option>,
169
);
171
- expect(node.innerHTML).toBe('1hello2');
172
- expect(node.value).toBe('hello');
170
+ expect(container.firstChild.innerHTML).toBe('1hello2');
171
+ expect(container.firstChild.value).toBe('hello');
172
});
173
174
// @gate enableBigIntSupport
176
- it('should support bigint values', () => {
177
- const node = ReactTestUtils.renderIntoDocument(<option>{5n}</option>);
178
- expect(node.innerHTML).toBe('5');
179
- expect(node.value).toBe('5');
175
+ it('should support bigint values', async () => {
176
+ const container = await renderIntoDocument(<option>{5n}</option>);
177
+ expect(container.firstChild.innerHTML).toBe('5');
178
+ expect(container.firstChild.value).toBe('5');
179
});
180
182
- it('should be able to use dangerouslySetInnerHTML on option', () => {
181
+ it('should be able to use dangerouslySetInnerHTML on option', async () => {
182
const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
184
- let node;
185
- expect(() => {
186
- node = ReactTestUtils.renderIntoDocument(stub);
183
+ let container;
184
+ await expect(async () => {
185
+ container = await renderIntoDocument(stub);
186
}).toErrorDev(
187
'Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.\n' +
188
' in option (at **)',
189
);
190
192
- expect(node.innerHTML).toBe('foobar');
191
+ expect(container.firstChild.innerHTML).toBe('foobar');
192
});
193
194
it('should set attribute for empty value', async () => {
packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+1
@@ -965,6 +965,7 @@ describe('ReactDOMSelect', () => {
965
expect(node.options[2].selected).toBe(false); // c
966
});
967
968
+ // @gate !disableLegacyMode
969
it('should allow controlling `value` in a nested legacy render', async () => {
970
let selectNode;
971
packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
+4
@@ -477,6 +477,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
477
resetModules();
478
});
479
480
+ // @gate !disableLegacyMode
481
it('legacy mode can explicitly ignore errors reconnecting different element types of children', () =>
482
expectMarkupMatch(
483
<div>
@@ -487,6 +488,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
488
</div>,
489
));
490
491
+ // @gate !disableLegacyMode
492
it('legacy mode can explicitly ignore reconnecting more children', () =>
493
expectMarkupMatch(
494
<div>
@@ -498,6 +500,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
500
</div>,
501
));
502
503
+ // @gate !disableLegacyMode
504
it('legacy mode can explicitly ignore reconnecting fewer children', () =>
505
expectMarkupMatch(
506
<div>
@@ -509,6 +512,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
512
</div>,
513
));
514
515
+ // @gate !disableLegacyMode
516
it('legacy mode can explicitly ignore reconnecting reordered children', () =>
517
expectMarkupMatch(
518
<div suppressHydrationWarning={true}>
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+1
@@ -718,6 +718,7 @@ describe('ReactDOMServerPartialHydration', () => {
718
expect(deleted.length).toBe(1);
719
});
720
721
+ // @gate !disableLegacyMode
722
it('warns and replaces the boundary content in legacy mode', async () => {
723
let suspend = false;
724
let resolve;
packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js
+3
@@ -996,16 +996,19 @@ describe('ReactDOM HostSingleton', () => {
996
});
997
998
// https://github.com/facebook/react/issues/26128
999
+ // @gate !disableLegacyMode
1000
it('(#26128) does not throw when rendering at body in legacy mode', async () => {
1001
ReactDOM.render(<div />, document.body);
1002
});
1003
1004
// https://github.com/facebook/react/issues/26128
1005
+ // @gate !disableLegacyMode
1006
it('(#26128) does not throw when rendering at <html> in legacy mode', async () => {
1007
ReactDOM.render(<body />, document.documentElement);
1008
});
1009
1010
// https://github.com/facebook/react/issues/26128
1011
+ // @gate !disableLegacyMode
1012
it('(#26128) does not throw when rendering at document in legacy mode', async () => {
1013
ReactDOM.render(<html />, document);
1014
});
packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
+3
@@ -100,6 +100,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
100
return text;
101
}
102
103
+ // @gate !disableLegacyMode
104
it('hides and unhides timed out DOM elements in legacy roots', async () => {
105
const divs = [
106
React.createRef(null),
@@ -160,6 +161,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
161
expect(container.textContent).toEqual('ABC');
162
});
163
164
+ // @gate !disableLegacyMode
165
it(
166
'in legacy roots, re-hides children if their display is updated ' +
167
'but the boundary is still showing the fallback',
@@ -213,6 +215,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
215
);
216
217
// Regression test for https://github.com/facebook/react/issues/14188
218
+ // @gate !disableLegacyMode
219
it('can call findDOMNode() in a suspended component commit phase in legacy roots', async () => {
220
const log = [];
221
const Lazy = React.lazy(
packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
+9
@@ -25,6 +25,7 @@ describe('ReactLegacyCompositeComponent', () => {
25
act = require('internal-test-utils').act;
26
});
27
28
+ // @gate !disableLegacyMode
29
it('should warn about `setState` in render in legacy mode', () => {
30
const container = document.createElement('div');
31
@@ -377,6 +378,7 @@ describe('ReactLegacyCompositeComponent', () => {
378
});
379
380
// @gate !disableLegacyContext
381
+ // @gate !disableLegacyMode
382
it('unmasked context propagates through updates', () => {
383
class Leaf extends React.Component {
384
static contextTypes = {
@@ -441,6 +443,7 @@ describe('ReactLegacyCompositeComponent', () => {
443
});
444
445
// @gate !disableLegacyContext
446
+ // @gate !disableLegacyMode
447
it('should trigger componentWillReceiveProps for context changes', () => {
448
let contextChanges = 0;
449
let propChanges = 0;
@@ -553,6 +556,7 @@ describe('ReactLegacyCompositeComponent', () => {
556
expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
557
});
558
559
+ // @gate !disableLegacyMode
560
it('only renders once if updated in componentWillReceiveProps in legacy mode', () => {
561
let renders = 0;
562
@@ -581,6 +585,7 @@ describe('ReactLegacyCompositeComponent', () => {
585
expect(instance.state.updated).toBe(true);
586
});
587
588
+ // @gate !disableLegacyMode
589
it('only renders once if updated in componentWillReceiveProps when batching in legacy mode', () => {
590
let renders = 0;
591
@@ -611,6 +616,7 @@ describe('ReactLegacyCompositeComponent', () => {
616
expect(instance.state.updated).toBe(true);
617
});
618
619
+ // @gate !disableLegacyMode
620
it('should update refs if shouldComponentUpdate gives false in legacy mode', () => {
621
class Static extends React.Component {
622
shouldComponentUpdate() {
@@ -665,6 +671,7 @@ describe('ReactLegacyCompositeComponent', () => {
671
expect(ReactDOM.findDOMNode(comp.static1Ref.current).textContent).toBe('A');
672
});
673
674
+ // @gate !disableLegacyMode
675
it('should allow access to findDOMNode in componentWillUnmount in legacy mode', () => {
676
let a = null;
677
let b = null;
@@ -693,6 +700,7 @@ describe('ReactLegacyCompositeComponent', () => {
700
});
701
702
// @gate !disableLegacyContext || !__DEV__
703
+ // @gate !disableLegacyMode
704
it('context should be passed down from the parent', () => {
705
class Parent extends React.Component {
706
static childContextTypes = {
@@ -813,6 +821,7 @@ describe('ReactLegacyCompositeComponent', () => {
821
expect(moo.state.amIImmutable).toBe(undefined);
822
});
823
824
+ // @gate !disableLegacyMode
825
it('should not warn about unmounting during unmounting in legacy mode', () => {
826
const container = document.createElement('div');
827
const layer = document.createElement('div');
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+42
@@ -588,6 +588,7 @@ describe('ReactLegacyErrorBoundaries', () => {
588
jest.restoreAllMocks();
589
});
590
591
+ // @gate !disableLegacyMode
592
it('does not swallow exceptions on mounting without boundaries', () => {
593
let container = document.createElement('div');
594
expect(() => {
@@ -605,6 +606,7 @@ describe('ReactLegacyErrorBoundaries', () => {
606
}).toThrow('Hello');
607
});
608
609
+ // @gate !disableLegacyMode
610
it('does not swallow exceptions on updating without boundaries', () => {
611
let container = document.createElement('div');
612
ReactDOM.render(<BrokenComponentWillUpdate />, container);
@@ -625,6 +627,7 @@ describe('ReactLegacyErrorBoundaries', () => {
627
}).toThrow('Hello');
628
});
629
630
+ // @gate !disableLegacyMode
631
it('does not swallow exceptions on unmounting without boundaries', () => {
632
const container = document.createElement('div');
633
ReactDOM.render(<BrokenComponentWillUnmount />, container);
@@ -633,6 +636,7 @@ describe('ReactLegacyErrorBoundaries', () => {
636
}).toThrow('Hello');
637
});
638
639
+ // @gate !disableLegacyMode
640
it('prevents errors from leaking into other roots', () => {
641
const container1 = document.createElement('div');
642
const container2 = document.createElement('div');
@@ -670,6 +674,7 @@ describe('ReactLegacyErrorBoundaries', () => {
674
expect(container3.firstChild).toBe(null);
675
});
676
677
+ // @gate !disableLegacyMode
678
it('logs a single error using both error boundaries', () => {
679
const container = document.createElement('div');
680
spyOnDev(console, 'error');
@@ -715,6 +720,7 @@ describe('ReactLegacyErrorBoundaries', () => {
720
expect(log).toEqual(['BothErrorBoundaries componentWillUnmount']);
721
});
722
723
+ // @gate !disableLegacyMode
724
it('renders an error state if child throws in render', () => {
725
const container = document.createElement('div');
726
ReactDOM.render(
@@ -745,6 +751,7 @@ describe('ReactLegacyErrorBoundaries', () => {
751
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
752
});
753
754
+ // @gate !disableLegacyMode
755
it('renders an error state if child throws in constructor', () => {
756
const container = document.createElement('div');
757
ReactDOM.render(
@@ -773,6 +780,7 @@ describe('ReactLegacyErrorBoundaries', () => {
780
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
781
});
782
783
+ // @gate !disableLegacyMode
784
it('renders an error state if child throws in componentWillMount', () => {
785
const container = document.createElement('div');
786
ReactDOM.render(
@@ -801,6 +809,7 @@ describe('ReactLegacyErrorBoundaries', () => {
809
});
810
811
// @gate !disableLegacyContext || !__DEV__
812
+ // @gate !disableLegacyMode
813
it('renders an error state if context provider throws in componentWillMount', () => {
814
class BrokenComponentWillMountWithContext extends React.Component {
815
static childContextTypes = {foo: PropTypes.number};
@@ -826,6 +835,7 @@ describe('ReactLegacyErrorBoundaries', () => {
835
});
836
837
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
838
+ // @gate !disableLegacyMode
839
it('renders an error state if module-style context provider throws in componentWillMount', () => {
840
function BrokenComponentWillMountWithContext() {
841
return {
@@ -864,6 +874,7 @@ describe('ReactLegacyErrorBoundaries', () => {
874
});
875
}
876
877
+ // @gate !disableLegacyMode
878
it('mounts the error message if mounting fails', () => {
879
function renderError(error) {
880
return <ErrorMessage message={error.message} />;
@@ -902,6 +913,7 @@ describe('ReactLegacyErrorBoundaries', () => {
913
]);
914
});
915
916
+ // @gate !disableLegacyMode
917
it('propagates errors on retry on mounting', () => {
918
const container = document.createElement('div');
919
ReactDOM.render(
@@ -946,6 +958,7 @@ describe('ReactLegacyErrorBoundaries', () => {
958
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
959
});
960
961
+ // @gate !disableLegacyMode
962
it('propagates errors inside boundary during componentWillMount', () => {
963
const container = document.createElement('div');
964
ReactDOM.render(
@@ -974,6 +987,7 @@ describe('ReactLegacyErrorBoundaries', () => {
987
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
988
});
989
990
+ // @gate !disableLegacyMode
991
it('propagates errors inside boundary while rendering error state', () => {
992
const container = document.createElement('div');
993
ReactDOM.render(
@@ -1017,6 +1031,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1031
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1032
});
1033
1034
+ // @gate !disableLegacyMode
1035
it('does not call componentWillUnmount when aborting initial mount', () => {
1036
const container = document.createElement('div');
1037
ReactDOM.render(
@@ -1056,6 +1071,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1071
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1072
});
1073
1074
+ // @gate !disableLegacyMode
1075
it('resets callback refs if mounting aborts', () => {
1076
function childRef(x) {
1077
log.push('Child ref is set to ' + x);
@@ -1100,6 +1116,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1116
]);
1117
});
1118
1119
+ // @gate !disableLegacyMode
1120
it('resets object refs if mounting aborts', () => {
1121
const childRef = React.createRef();
1122
const errorMessageRef = React.createRef();
@@ -1140,6 +1157,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1157
expect(errorMessageRef.current).toEqual(null);
1158
});
1159
1160
+ // @gate !disableLegacyMode
1161
it('successfully mounts if no error occurs', () => {
1162
const container = document.createElement('div');
1163
ReactDOM.render(
@@ -1161,6 +1179,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1179
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1180
});
1181
1182
+ // @gate !disableLegacyMode
1183
it('catches if child throws in constructor during update', () => {
1184
const container = document.createElement('div');
1185
ReactDOM.render(
@@ -1209,6 +1228,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1228
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1229
});
1230
1231
+ // @gate !disableLegacyMode
1232
it('catches if child throws in componentWillMount during update', () => {
1233
const container = document.createElement('div');
1234
ReactDOM.render(
@@ -1258,6 +1278,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1278
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1279
});
1280
1281
+ // @gate !disableLegacyMode
1282
it('catches if child throws in componentWillReceiveProps during update', () => {
1283
const container = document.createElement('div');
1284
ReactDOM.render(
@@ -1302,6 +1323,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1323
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1324
});
1325
1326
+ // @gate !disableLegacyMode
1327
it('catches if child throws in componentWillUpdate during update', () => {
1328
const container = document.createElement('div');
1329
ReactDOM.render(
@@ -1347,6 +1369,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1369
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1370
});
1371
1372
+ // @gate !disableLegacyMode
1373
it('catches if child throws in render during update', () => {
1374
const container = document.createElement('div');
1375
ReactDOM.render(
@@ -1396,6 +1419,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1419
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1420
});
1421
1422
+ // @gate !disableLegacyMode
1423
it('keeps refs up-to-date during updates', () => {
1424
function child1Ref(x) {
1425
log.push('Child1 ref is set to ' + x);
@@ -1460,6 +1484,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1484
]);
1485
});
1486
1487
+ // @gate !disableLegacyMode
1488
it('recovers from componentWillUnmount errors on update', () => {
1489
const container = document.createElement('div');
1490
ReactDOM.render(
@@ -1516,6 +1541,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1541
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1542
});
1543
1544
+ // @gate !disableLegacyMode
1545
it('recovers from nested componentWillUnmount errors on update', () => {
1546
const container = document.createElement('div');
1547
ReactDOM.render(
@@ -1577,6 +1603,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1603
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1604
});
1605
1606
+ // @gate !disableLegacyMode
1607
it('picks the right boundary when handling unmounting errors', () => {
1608
function renderInnerError(error) {
1609
return <div>Caught an inner error: {error.message}.</div>;
@@ -1646,6 +1673,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1673
]);
1674
});
1675
1676
+ // @gate !disableLegacyMode
1677
it('can recover from error state', () => {
1678
const container = document.createElement('div');
1679
ReactDOM.render(
@@ -1694,6 +1722,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1722
]);
1723
});
1724
1725
+ // @gate !disableLegacyMode
1726
it('can update multiple times in error state', () => {
1727
const container = document.createElement('div');
1728
ReactDOM.render(
@@ -1718,6 +1747,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1747
ReactDOM.unmountComponentAtNode(container);
1748
});
1749
1750
+ // @gate !disableLegacyMode
1751
it("doesn't get into inconsistent state during removals", () => {
1752
const container = document.createElement('div');
1753
ReactDOM.render(
@@ -1737,6 +1767,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1767
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1768
});
1769
1770
+ // @gate !disableLegacyMode
1771
it("doesn't get into inconsistent state during additions", () => {
1772
const container = document.createElement('div');
1773
ReactDOM.render(<ErrorBoundary />, container);
@@ -1755,6 +1786,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1786
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1787
});
1788
1789
+ // @gate !disableLegacyMode
1790
it("doesn't get into inconsistent state during reorders", () => {
1791
function getAMixOfNormalAndBrokenRenderElements() {
1792
const elements = [];
@@ -1803,6 +1835,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1835
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1836
});
1837
1838
+ // @gate !disableLegacyMode
1839
it('catches errors originating downstream', () => {
1840
let fail = false;
1841
class Stateful extends React.Component {
@@ -1845,6 +1878,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1878
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1879
});
1880
1881
+ // @gate !disableLegacyMode
1882
it('catches errors in componentDidMount', () => {
1883
const container = document.createElement('div');
1884
ReactDOM.render(
@@ -1904,6 +1938,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1938
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1939
});
1940
1941
+ // @gate !disableLegacyMode
1942
it('catches errors in componentDidUpdate', () => {
1943
const container = document.createElement('div');
1944
ReactDOM.render(
@@ -1943,6 +1978,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1978
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1979
});
1980
1981
+ // @gate !disableLegacyMode
1982
it('propagates errors inside boundary during componentDidMount', () => {
1983
const container = document.createElement('div');
1984
ReactDOM.render(
@@ -1980,6 +2016,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2016
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
2017
});
2018
2019
+ // @gate !disableLegacyMode
2020
it('calls componentDidCatch for each error that is captured', () => {
2021
function renderUnmountError(error) {
2022
return <div>Caught an unmounting error: {error.message}.</div>;
@@ -2081,6 +2118,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2118
]);
2119
});
2120
2121
+ // @gate !disableLegacyMode
2122
it('discards a bad root if the root component fails', () => {
2123
const X = null;
2124
const Y = undefined;
@@ -2112,6 +2150,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2150
expect(err2.message).toMatch(/got: undefined/);
2151
});
2152
2153
+ // @gate !disableLegacyMode
2154
it('renders empty output if error boundary does not handle the error', () => {
2155
const container = document.createElement('div');
2156
expect(() => {
@@ -2146,6 +2185,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2185
expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']);
2186
});
2187
2188
+ // @gate !disableLegacyMode
2189
it('passes first error when two errors happen in commit', () => {
2190
const errors = [];
2191
let caughtError;
@@ -2185,6 +2225,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2225
expect(caughtError.message).toBe('child sad');
2226
});
2227
2228
+ // @gate !disableLegacyMode
2229
it('propagates uncaught error inside unbatched initial mount', () => {
2230
function Foo() {
2231
throw new Error('foo error');
@@ -2197,6 +2238,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2238
}).toThrow('foo error');
2239
});
2240
2241
+ // @gate !disableLegacyMode
2242
it('handles errors that occur in before-mutation commit hook', () => {
2243
const errors = [];
2244
let caughtError;
packages/react-dom/src/__tests__/ReactLegacyMount-test.js
+23
@@ -37,6 +37,7 @@ describe('ReactMount', () => {
37
});
38
39
describe('unmountComponentAtNode', () => {
40
+ // @gate !disableLegacyMode
41
it('throws when given a non-node', () => {
42
const nodeArray = document.getElementsByTagName('div');
43
expect(() => {
@@ -44,6 +45,7 @@ describe('ReactMount', () => {
45
}).toThrowError('Target container is not a DOM element.');
46
});
47
48
+ // @gate !disableLegacyMode
49
it('returns false on non-React containers', () => {
50
const d = document.createElement('div');
51
d.innerHTML = '<b>hellooo</b>';
@@ -51,6 +53,7 @@ describe('ReactMount', () => {
53
expect(d.textContent).toBe('hellooo');
54
});
55
56
+ // @gate !disableLegacyMode
57
it('returns true on React containers', () => {
58
const d = document.createElement('div');
59
ReactDOM.render(<b>hellooo</b>, d);
@@ -60,6 +63,7 @@ describe('ReactMount', () => {
63
});
64
});
65
66
+ // @gate !disableLegacyMode
67
it('warns when given a factory', () => {
68
class Component extends React.Component {
69
render() {
@@ -76,6 +80,7 @@ describe('ReactMount', () => {
80
);
81
});
82
83
+ // @gate !disableLegacyMode
84
it('should render different components in same root', () => {
85
const container = document.createElement('container');
86
document.body.appendChild(container);
@@ -87,6 +92,7 @@ describe('ReactMount', () => {
92
expect(container.firstChild.nodeName).toBe('SPAN');
93
});
94
95
+ // @gate !disableLegacyMode
96
it('should unmount and remount if the key changes', () => {
97
const container = document.createElement('container');
98
@@ -122,6 +128,7 @@ describe('ReactMount', () => {
128
expect(mockUnmount).toHaveBeenCalledTimes(1);
129
});
130
131
+ // @gate !disableLegacyMode
132
it('should reuse markup if rendering to the same target twice', () => {
133
const container = document.createElement('container');
134
const instance1 = ReactDOM.render(<div />, container);
@@ -130,6 +137,7 @@ describe('ReactMount', () => {
137
expect(instance1 === instance2).toBe(true);
138
});
139
140
+ // @gate !disableLegacyMode
141
it('does not warn if mounting into left padded rendered markup', () => {
142
const container = document.createElement('container');
143
container.innerHTML = ReactDOMServer.renderToString(<div />) + ' ';
@@ -138,6 +146,7 @@ describe('ReactMount', () => {
146
ReactDOM.hydrate(<div />, container);
147
});
148
149
+ // @gate !disableLegacyMode
150
it('should warn if mounting into right padded rendered markup', () => {
151
const container = document.createElement('container');
152
container.innerHTML = ' ' + ReactDOMServer.renderToString(<div />);
@@ -147,6 +156,7 @@ describe('ReactMount', () => {
156
);
157
});
158
159
+ // @gate !disableLegacyMode
160
it('should not warn if mounting into non-empty node', () => {
161
const container = document.createElement('container');
162
container.innerHTML = '<div></div>';
@@ -154,6 +164,7 @@ describe('ReactMount', () => {
164
ReactDOM.render(<div />, container);
165
});
166
167
+ // @gate !disableLegacyMode
168
it('should warn when mounting into document.body', () => {
169
const iFrame = document.createElement('iframe');
170
document.body.appendChild(iFrame);
@@ -162,6 +173,7 @@ describe('ReactMount', () => {
173
ReactDOM.render(<div />, iFrame.contentDocument.body);
174
});
175
176
+ // @gate !disableLegacyMode
177
it('should account for escaping on a checksum mismatch', () => {
178
const div = document.createElement('div');
179
const markup = ReactDOMServer.renderToString(
@@ -180,6 +192,7 @@ describe('ReactMount', () => {
192
);
193
});
194
195
+ // @gate !disableLegacyMode
196
it('should warn if render removes React-rendered children', () => {
197
const container = document.createElement('container');
198
@@ -207,6 +220,7 @@ describe('ReactMount', () => {
220
);
221
});
222
223
+ // @gate !disableLegacyMode
224
it('should warn if the unmounted node was rendered by another copy of React', () => {
225
jest.resetModules();
226
const ReactDOMOther = require('react-dom');
@@ -236,6 +250,7 @@ describe('ReactMount', () => {
250
ReactDOM.unmountComponentAtNode(container);
251
});
252
253
+ // @gate !disableLegacyMode
254
it('passes the correct callback context', () => {
255
const container = document.createElement('div');
256
let calls = 0;
@@ -276,6 +291,7 @@ describe('ReactMount', () => {
291
expect(calls).toBe(5);
292
});
293
294
+ // @gate !disableLegacyMode
295
it('initial mount of legacy root is sync inside batchedUpdates, as if it were wrapped in flushSync', () => {
296
const container1 = document.createElement('div');
297
const container2 = document.createElement('div');
@@ -322,6 +338,7 @@ describe('ReactMount', () => {
338
expect(mountPoint.nodeType).toBe(COMMENT_NODE);
339
});
340
341
+ // @gate !disableLegacyMode
342
it('renders at a comment node', () => {
343
function Char(props) {
344
return props.children;
@@ -347,6 +364,7 @@ describe('ReactMount', () => {
364
});
365
});
366
367
+ // @gate !disableLegacyMode
368
it('clears existing children with legacy API', async () => {
369
const container = document.createElement('div');
370
container.innerHTML = '<div>a</div><div>b</div>';
@@ -369,6 +387,7 @@ describe('ReactMount', () => {
387
expect(container.textContent).toEqual('dc');
388
});
389
390
+ // @gate !disableLegacyMode
391
it('warns when rendering with legacy API into createRoot() container', async () => {
392
const container = document.createElement('div');
393
const root = ReactDOMClient.createRoot(container);
@@ -393,6 +412,7 @@ describe('ReactMount', () => {
412
expect(container.textContent).toEqual('Bye');
413
});
414
415
+ // @gate !disableLegacyMode
416
it('callback passed to legacy hydrate() API', () => {
417
const container = document.createElement('div');
418
container.innerHTML = '<div>Hi</div>';
@@ -403,6 +423,7 @@ describe('ReactMount', () => {
423
assertLog(['callback']);
424
});
425
426
+ // @gate !disableLegacyMode
427
it('warns when unmounting with legacy API (no previous content)', async () => {
428
const container = document.createElement('div');
429
const root = ReactDOMClient.createRoot(container);
@@ -430,6 +451,7 @@ describe('ReactMount', () => {
451
expect(container.textContent).toEqual('');
452
});
453
454
+ // @gate !disableLegacyMode
455
it('warns when unmounting with legacy API (has previous content)', async () => {
456
const container = document.createElement('div');
457
// Currently createRoot().render() doesn't clear this.
@@ -458,6 +480,7 @@ describe('ReactMount', () => {
480
expect(container.textContent).toEqual('');
481
});
482
483
+ // @gate !disableLegacyMode
484
it('warns when passing legacy container to createRoot()', () => {
485
const container = document.createElement('div');
486
ReactDOM.render(<div>Hi</div>, container);
packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js
+2
@@ -13,6 +13,7 @@ describe('ReactDOMRoot', () => {
13
jest.restoreAllMocks();
14
});
15
16
+ // @gate !disableLegacyMode
17
test('deprecation warning for ReactDOM.render', () => {
18
spyOnDev(console, 'error');
19
@@ -26,6 +27,7 @@ describe('ReactDOMRoot', () => {
27
}
28
});
29
30
+ // @gate !disableLegacyMode
31
test('deprecation warning for ReactDOM.hydrate', () => {
32
spyOnDev(console, 'error');
33
packages/react-dom/src/__tests__/ReactLegacyUpdates-test.js
+39
@@ -31,6 +31,7 @@ describe('ReactLegacyUpdates', () => {
31
assertLog = InternalTestUtils.assertLog;
32
});
33
34
+ // @gate !disableLegacyMode
35
it('should batch state when updating state twice', () => {
36
let updateCount = 0;
37
@@ -60,6 +61,7 @@ describe('ReactLegacyUpdates', () => {
61
expect(updateCount).toBe(1);
62
});
63
64
+ // @gate !disableLegacyMode
65
it('should batch state when updating two different state keys', () => {
66
let updateCount = 0;
67
@@ -92,6 +94,7 @@ describe('ReactLegacyUpdates', () => {
94
expect(updateCount).toBe(1);
95
});
96
97
+ // @gate !disableLegacyMode
98
it('should batch state and props together', () => {
99
let updateCount = 0;
100
@@ -125,6 +128,7 @@ describe('ReactLegacyUpdates', () => {
128
expect(updateCount).toBe(1);
129
});
130
131
+ // @gate !disableLegacyMode
132
it('should batch parent/child state updates together', () => {
133
let parentUpdateCount = 0;
134
@@ -179,6 +183,7 @@ describe('ReactLegacyUpdates', () => {
183
expect(childUpdateCount).toBe(1);
184
});
185
186
+ // @gate !disableLegacyMode
187
it('should batch child/parent state updates together', () => {
188
let parentUpdateCount = 0;
189
@@ -235,6 +240,7 @@ describe('ReactLegacyUpdates', () => {
240
expect(childUpdateCount).toBe(1);
241
});
242
243
+ // @gate !disableLegacyMode
244
it('should support chained state updates', () => {
245
let updateCount = 0;
246
@@ -274,6 +280,7 @@ describe('ReactLegacyUpdates', () => {
280
expect(updateCount).toBe(2);
281
});
282
283
+ // @gate !disableLegacyMode
284
it('should batch forceUpdate together', () => {
285
let shouldUpdateCount = 0;
286
let updateCount = 0;
@@ -316,6 +323,7 @@ describe('ReactLegacyUpdates', () => {
323
expect(updateCount).toBe(1);
324
});
325
326
+ // @gate !disableLegacyMode
327
it('should update children even if parent blocks updates', () => {
328
let parentRenderCount = 0;
329
let childRenderCount = 0;
@@ -364,6 +372,7 @@ describe('ReactLegacyUpdates', () => {
372
expect(childRenderCount).toBe(2);
373
});
374
375
+ // @gate !disableLegacyMode
376
it('should not reconcile children passed via props', () => {
377
let numMiddleRenders = 0;
378
let numBottomRenders = 0;
@@ -401,6 +410,7 @@ describe('ReactLegacyUpdates', () => {
410
expect(numBottomRenders).toBe(1);
411
});
412
413
+ // @gate !disableLegacyMode
414
it('should flow updates correctly', () => {
415
let willUpdates = [];
416
let didUpdates = [];
@@ -530,6 +540,7 @@ describe('ReactLegacyUpdates', () => {
540
);
541
});
542
543
+ // @gate !disableLegacyMode
544
it('should queue mount-ready handlers across different roots', () => {
545
// We'll define two components A and B, then update both of them. When A's
546
// componentDidUpdate handlers is called, B's DOM should already have been
@@ -579,6 +590,7 @@ describe('ReactLegacyUpdates', () => {
590
expect(aUpdated).toBe(true);
591
});
592
593
+ // @gate !disableLegacyMode
594
it('should flush updates in the correct order', () => {
595
const updates = [];
596
@@ -661,6 +673,7 @@ describe('ReactLegacyUpdates', () => {
673
/* eslint-enable indent */
674
});
675
676
+ // @gate !disableLegacyMode
677
it('should flush updates in the correct order across roots', () => {
678
const instances = [];
679
const updates = [];
@@ -699,6 +712,7 @@ describe('ReactLegacyUpdates', () => {
712
expect(updates).toEqual([0, 1, 2, 0, 1, 2]);
713
});
714
715
+ // @gate !disableLegacyMode
716
it('should queue nested updates', () => {
717
// See https://github.com/facebook/react/issues/1147
718
@@ -752,6 +766,7 @@ describe('ReactLegacyUpdates', () => {
766
expect(ReactDOM.findDOMNode(x).textContent).toBe('1');
767
});
768
769
+ // @gate !disableLegacyMode
770
it('should queue updates from during mount', () => {
771
// See https://github.com/facebook/react/issues/1353
772
let a;
@@ -791,6 +806,7 @@ describe('ReactLegacyUpdates', () => {
806
expect(ReactDOM.findDOMNode(a).textContent).toBe('A1');
807
});
808
809
+ // @gate !disableLegacyMode
810
it('calls componentWillReceiveProps setState callback properly', () => {
811
let callbackCount = 0;
812
@@ -817,6 +833,7 @@ describe('ReactLegacyUpdates', () => {
833
expect(callbackCount).toBe(1);
834
});
835
836
+ // @gate !disableLegacyMode
837
it('does not call render after a component as been deleted', () => {
838
let renderCount = 0;
839
let componentB = null;
@@ -854,6 +871,7 @@ describe('ReactLegacyUpdates', () => {
871
expect(renderCount).toBe(1);
872
});
873
874
+ // @gate !disableLegacyMode
875
it('throws in setState if the update callback is not a function', () => {
876
function Foo() {
877
this.a = 1;
@@ -897,6 +915,7 @@ describe('ReactLegacyUpdates', () => {
915
);
916
});
917
918
+ // @gate !disableLegacyMode
919
it('throws in forceUpdate if the update callback is not a function', () => {
920
function Foo() {
921
this.a = 1;
@@ -940,6 +959,7 @@ describe('ReactLegacyUpdates', () => {
959
);
960
});
961
962
+ // @gate !disableLegacyMode
963
it('does not update one component twice in a batch (#2410)', () => {
964
class Parent extends React.Component {
965
childRef = React.createRef();
@@ -992,6 +1012,7 @@ describe('ReactLegacyUpdates', () => {
1012
});
1013
});
1014
1015
+ // @gate !disableLegacyMode
1016
it('does not update one component twice in a batch (#6371)', () => {
1017
let callbacks = [];
1018
function emitChange() {
@@ -1049,6 +1070,7 @@ describe('ReactLegacyUpdates', () => {
1070
expect(result).toEqual(42);
1071
});
1072
1073
+ // @gate !disableLegacyMode
1074
it('unmounts and remounts a root in the same batch', () => {
1075
const container = document.createElement('div');
1076
ReactDOM.render(<span>a</span>, container);
@@ -1059,6 +1081,7 @@ describe('ReactLegacyUpdates', () => {
1081
expect(container.textContent).toBe('b');
1082
});
1083
1084
+ // @gate !disableLegacyMode
1085
it('handles reentrant mounting in synchronous mode', () => {
1086
let mounts = 0;
1087
class Editor extends React.Component {
@@ -1096,6 +1119,7 @@ describe('ReactLegacyUpdates', () => {
1119
expect(mounts).toBe(1);
1120
});
1121
1122
+ // @gate !disableLegacyMode
1123
it('mounts and unmounts are sync even in a batch', () => {
1124
const ops = [];
1125
const container = document.createElement('div');
@@ -1108,6 +1132,7 @@ describe('ReactLegacyUpdates', () => {
1132
expect(ops).toEqual(['Hello', '']);
1133
});
1134
1135
+ // @gate !disableLegacyMode
1136
it(
1137
'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1138
'should both flush in the immediately subsequent commit',
@@ -1151,6 +1176,7 @@ describe('ReactLegacyUpdates', () => {
1176
},
1177
);
1178
1179
+ // @gate !disableLegacyMode
1180
it(
1181
'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1182
'(on a sibling) should both flush in the immediately subsequent commit',
@@ -1222,6 +1248,7 @@ describe('ReactLegacyUpdates', () => {
1248
},
1249
);
1250
1251
+ // @gate !disableLegacyMode
1252
it('uses correct base state for setState inside render phase', () => {
1253
const ops = [];
1254
@@ -1245,6 +1272,7 @@ describe('ReactLegacyUpdates', () => {
1272
expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
1273
});
1274
1275
+ // @gate !disableLegacyMode
1276
it('does not re-render if state update is null', () => {
1277
const container = document.createElement('div');
1278
@@ -1265,6 +1293,7 @@ describe('ReactLegacyUpdates', () => {
1293
});
1294
1295
// Will change once we switch to async by default
1296
+ // @gate !disableLegacyMode
1297
it('synchronously renders hidden subtrees', () => {
1298
const container = document.createElement('div');
1299
let ops = [];
@@ -1301,6 +1330,7 @@ describe('ReactLegacyUpdates', () => {
1330
expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1331
});
1332
1333
+ // @gate !disableLegacyMode
1334
it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1335
class Foo extends React.Component {
1336
componentDidMount() {
@@ -1325,6 +1355,7 @@ describe('ReactLegacyUpdates', () => {
1355
ReactDOM.render(<Foo />, container);
1356
});
1357
1358
+ // @gate !disableLegacyMode
1359
it('resets the update counter for unrelated updates', () => {
1360
const container = document.createElement('div');
1361
const ref = React.createRef();
@@ -1365,6 +1396,7 @@ describe('ReactLegacyUpdates', () => {
1396
expect(ref.current).toBe(null);
1397
});
1398
1399
+ // @gate !disableLegacyMode
1400
it('does not fall into an infinite update loop', () => {
1401
class NonTerminating extends React.Component {
1402
state = {step: 0};
@@ -1390,6 +1422,7 @@ describe('ReactLegacyUpdates', () => {
1422
}).toThrow('Maximum');
1423
});
1424
1425
+ // @gate !disableLegacyMode
1426
it('does not fall into an infinite update loop with useLayoutEffect', () => {
1427
function NonTerminating() {
1428
const [step, setStep] = React.useState(0);
@@ -1405,6 +1438,7 @@ describe('ReactLegacyUpdates', () => {
1438
}).toThrow('Maximum');
1439
});
1440
1441
+ // @gate !disableLegacyMode
1442
it('can recover after falling into an infinite update loop', () => {
1443
class NonTerminating extends React.Component {
1444
state = {step: 0};
@@ -1445,6 +1479,7 @@ describe('ReactLegacyUpdates', () => {
1479
expect(container.textContent).toBe('1');
1480
});
1481
1482
+ // @gate !disableLegacyMode
1483
it('does not fall into mutually recursive infinite update loop with same container', () => {
1484
// Note: this test would fail if there were two or more different roots.
1485
@@ -1472,6 +1507,7 @@ describe('ReactLegacyUpdates', () => {
1507
}).toThrow('Maximum');
1508
});
1509
1510
+ // @gate !disableLegacyMode
1511
it('does not fall into an infinite error loop', () => {
1512
function BadRender() {
1513
throw new Error('error');
@@ -1505,6 +1541,7 @@ describe('ReactLegacyUpdates', () => {
1541
}).toThrow('Maximum');
1542
});
1543
1544
+ // @gate !disableLegacyMode
1545
it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
1546
const subscribers = [];
1547
@@ -1540,6 +1577,7 @@ describe('ReactLegacyUpdates', () => {
1577
1578
// TODO: Replace this branch with @gate pragmas
1579
if (__DEV__) {
1580
+ // @gate !disableLegacyMode
1581
it('can have nested updates if they do not cross the limit', async () => {
1582
let _setStep;
1583
const LIMIT = 50;
@@ -1567,6 +1605,7 @@ describe('ReactLegacyUpdates', () => {
1605
expect(container.textContent).toBe('50');
1606
});
1607
1608
+ // @gate !disableLegacyMode
1609
it('can have many updates inside useEffect without triggering a warning', async () => {
1610
function Terminating() {
1611
const [step, setStep] = React.useState(0);
packages/react-dom/src/__tests__/ReactMountDestruction-test.js
+2
@@ -49,6 +49,7 @@ describe('ReactMount', () => {
49
});
50
});
51
52
+ // @gate !disableLegacyMode
53
it('should warn when unmounting a non-container root node', () => {
54
const mainContainerDiv = document.createElement('div');
55
@@ -71,6 +72,7 @@ describe('ReactMount', () => {
72
);
73
});
74
75
+ // @gate !disableLegacyMode
76
it('should warn when unmounting a non-container, non-root node', () => {
77
const mainContainerDiv = document.createElement('div');
78
packages/react-dom/src/__tests__/ReactRenderDocument-test.js
+1
@@ -373,6 +373,7 @@ describe('rendering React components at document', () => {
373
expect(testDocument.body.innerHTML).toBe('Hello world');
374
});
375
376
+ // @gate !disableLegacyMode
377
it('supports findDOMNode on full-page components in legacy mode', () => {
378
const tree = (
379
<html>
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+3
@@ -505,6 +505,7 @@ describe('ReactDOMServerHydration', () => {
505
await act(() => root.render(<div />));
506
});
507
508
+ // @gate !disableLegacyMode
509
it('Suspense + hydration in legacy mode', () => {
510
const element = document.createElement('div');
511
element.innerHTML = '<div><div>Hello World</div></div>';
@@ -530,6 +531,7 @@ describe('ReactDOMServerHydration', () => {
531
expect(element.innerHTML).toBe('<div><div>Hello World</div></div>');
532
});
533
534
+ // @gate !disableLegacyMode
535
it('Suspense + hydration in legacy mode (at root)', () => {
536
const element = document.createElement('div');
537
element.innerHTML = '<div>Hello World</div>';
@@ -550,6 +552,7 @@ describe('ReactDOMServerHydration', () => {
552
);
553
});
554
555
+ // @gate !disableLegacyMode
556
it('Suspense + hydration in legacy mode with no fallback', () => {
557
const element = document.createElement('div');
558
element.innerHTML = '<div>Hello World</div>';
packages/react-dom/src/__tests__/findDOMNode-test.js
+6
@@ -19,6 +19,7 @@ describe('findDOMNode', () => {
19
expect(ReactDOM.findDOMNode(null)).toBe(null);
20
});
21
22
+ // @gate !disableLegacyMode
23
it('findDOMNode should find dom element', () => {
24
class MyNode extends React.Component {
25
render() {
@@ -37,6 +38,7 @@ describe('findDOMNode', () => {
38
expect(mySameDiv).toBe(myDiv);
39
});
40
41
+ // @gate !disableLegacyMode
42
it('findDOMNode should find dom element after an update from null', () => {
43
function Bar({flag}) {
44
if (flag) {
@@ -69,6 +71,7 @@ describe('findDOMNode', () => {
71
}).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
72
});
73
74
+ // @gate !disableLegacyMode
75
it('findDOMNode should reject unmounted objects with render func', () => {
76
class Foo extends React.Component {
77
render() {
@@ -85,6 +88,7 @@ describe('findDOMNode', () => {
88
);
89
});
90
91
+ // @gate !disableLegacyMode
92
it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
93
class Bar extends React.Component {
94
UNSAFE_componentWillMount() {
@@ -98,6 +102,7 @@ describe('findDOMNode', () => {
102
expect(() => ReactTestUtils.renderIntoDocument(<Bar />)).not.toThrow();
103
});
104
105
+ // @gate !disableLegacyMode
106
it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
107
let parent = undefined;
108
let child = undefined;
@@ -129,6 +134,7 @@ describe('findDOMNode', () => {
134
expect(match).toBe(child);
135
});
136
137
+ // @gate !disableLegacyMode
138
it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
139
let parent = undefined;
140
let child = undefined;
packages/react-dom/src/__tests__/refsLegacy-test.js
+1
@@ -19,6 +19,7 @@ describe('root level refs with legacy APIs', () => {
19
ReactDOM = require('react-dom');
20
});
21
22
+ // @gate !disableLegacyMode
23
it('attaches and detaches root refs', () => {
24
let inst = null;
25
packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js
+7
-39
@@ -20,6 +20,7 @@ const renderSubtreeIntoContainer =
20
21
describe('renderSubtreeIntoContainer', () => {
22
// @gate !disableLegacyContext
23
+ // @gate !disableLegacyMode
24
it('should pass context when rendering subtree elsewhere', () => {
25
const portal = document.createElement('div');
26
@@ -63,46 +64,8 @@ describe('renderSubtreeIntoContainer', () => {
64
expect(portal.firstChild.innerHTML).toBe('bar');
65
});
66
66
- it('should throw if parentComponent is invalid', () => {
67
- const portal = document.createElement('div');
68
-
69
- class Component extends React.Component {
70
- static contextTypes = {
71
- foo: PropTypes.string.isRequired,
72
- };
73
-
74
- render() {
75
- return <div>{this.context.foo}</div>;
76
- }
77
- }
78
-
79
- // ESLint is confused here and thinks Parent is unused, presumably because
80
- // it is only used inside of the class body?
81
- // eslint-disable-next-line no-unused-vars
82
- class Parent extends React.Component {
83
- static childContextTypes = {
84
- foo: PropTypes.string.isRequired,
85
- };
86
-
87
- getChildContext() {
88
- return {
89
- foo: 'bar',
90
- };
91
- }
92
-
93
- render() {
94
- return null;
95
- }
96
-
97
- componentDidMount() {
98
- expect(function () {
99
- renderSubtreeIntoContainer(<Parent />, <Component />, portal);
100
- }).toThrowError('parentComponentmust be a valid React Component');
101
- }
102
- }
103
- });
104
-
67
// @gate !disableLegacyContext
68
+ // @gate !disableLegacyMode
69
it('should update context if it changes due to setState', async () => {
70
const container = document.createElement('div');
71
document.body.appendChild(container);
@@ -171,6 +134,7 @@ describe('renderSubtreeIntoContainer', () => {
134
});
135
136
// @gate !disableLegacyContext
137
+ // @gate !disableLegacyMode
138
it('should update context if it changes due to re-render', async () => {
139
const container = document.createElement('div');
140
document.body.appendChild(container);
@@ -232,6 +196,7 @@ describe('renderSubtreeIntoContainer', () => {
196
expect(portal.firstChild.innerHTML).toBe('changed-changed');
197
});
198
199
+ // @gate !disableLegacyMode
200
it('should render portal with non-context-provider parent', async () => {
201
const container = document.createElement('div');
202
document.body.appendChild(container);
@@ -259,6 +224,7 @@ describe('renderSubtreeIntoContainer', () => {
224
});
225
226
// @gate !disableLegacyContext
227
+ // @gate !disableLegacyMode
228
it('should get context through non-context-provider parent', async () => {
229
const container = document.createElement('div');
230
document.body.appendChild(container);
@@ -306,6 +272,7 @@ describe('renderSubtreeIntoContainer', () => {
272
});
273
274
// @gate !disableLegacyContext
275
+ // @gate !disableLegacyMode
276
it('should get context through middle non-context-provider layer', async () => {
277
const container = document.createElement('div');
278
document.body.appendChild(container);
@@ -360,6 +327,7 @@ describe('renderSubtreeIntoContainer', () => {
327
expect(portal2.textContent).toBe('foo');
328
});
329
330
+ // @gate !disableLegacyMode
331
it('legacy test: fails gracefully when mixing React 15 and 16', () => {
332
class C extends React.Component {
333
render() {
packages/react-dom/src/client/ReactDOMLegacy.js
+25
@@ -14,6 +14,7 @@ import type {
14
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
15
import type {ReactNodeList} from 'shared/ReactTypes';
16
17
+import {disableLegacyMode} from 'shared/ReactFeatureFlags';
18
import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
19
import {
20
getInstanceFromNode,
@@ -264,6 +265,14 @@ export function hydrate(
265
container: Container,
266
callback: ?Function,
267
): React$Component<any, any> | PublicInstance | null {
268
+ if (disableLegacyMode) {
269
+ if (__DEV__) {
270
+ console.error(
271
+ 'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead',
272
+ );
273
+ }
274
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
275
+ }
276
if (__DEV__) {
277
console.error(
278
'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
@@ -304,6 +313,14 @@ export function render(
313
container: Container,
314
callback: ?Function,
315
): React$Component<any, any> | PublicInstance | null {
316
+ if (disableLegacyMode) {
317
+ if (__DEV__) {
318
+ console.error(
319
+ 'ReactDOM.render is no longer supported in React 18. Use createRoot instead.',
320
+ );
321
+ }
322
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
323
+ }
324
if (__DEV__) {
325
console.error(
326
'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
@@ -344,6 +361,14 @@ export function unstable_renderSubtreeIntoContainer(
361
containerNode: Container,
362
callback: ?Function,
363
): React$Component<any, any> | PublicInstance | null {
364
+ if (disableLegacyMode) {
365
+ if (__DEV__) {
366
+ console.error(
367
+ 'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported in React 18. Consider using a portal instead.',
368
+ );
369
+ }
370
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
371
+ }
372
if (__DEV__) {
373
console.error(
374
'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js
+68
-46
@@ -433,7 +433,7 @@ describe('ChangeEventPlugin', () => {
433
expect(called2).toBe(1);
434
});
435
436
- it('should deduplicate input value change events', () => {
436
+ it('should deduplicate input value change events', async () => {
437
let called = 0;
438
439
function cb(e) {
@@ -441,59 +441,81 @@ describe('ChangeEventPlugin', () => {
441
expect(e.type).toBe('change');
442
}
443
444
- let input;
445
- ['text', 'number', 'range'].forEach(type => {
444
+ const inputTypes = ['text', 'number', 'range'];
445
+ while (inputTypes.length) {
446
+ const type = inputTypes.pop();
447
called = 0;
447
- input = ReactDOM.render(<input type={type} onChange={cb} />, container);
448
- // Should be ignored (no change):
449
- input.dispatchEvent(
450
- new Event('change', {bubbles: true, cancelable: true}),
451
- );
452
- setUntrackedValue.call(input, '42');
453
- input.dispatchEvent(
454
- new Event('change', {bubbles: true, cancelable: true}),
455
- );
456
- // Should be ignored (no change):
457
- input.dispatchEvent(
458
- new Event('change', {bubbles: true, cancelable: true}),
459
- );
448
+ let root = ReactDOMClient.createRoot(container);
449
+ let ref = {current: null};
450
+ await act(() => {
451
+ root.render(<input ref={ref} type={type} onChange={cb} />);
452
+ });
453
+ let input = ref.current;
454
+ await act(() => {
455
+ // Should be ignored (no change):
456
+ input.dispatchEvent(
457
+ new Event('change', {bubbles: true, cancelable: true}),
458
+ );
459
+ setUntrackedValue.call(input, '42');
460
+ input.dispatchEvent(
461
+ new Event('change', {bubbles: true, cancelable: true}),
462
+ );
463
+ // Should be ignored (no change):
464
+ input.dispatchEvent(
465
+ new Event('change', {bubbles: true, cancelable: true}),
466
+ );
467
+ });
468
expect(called).toBe(1);
461
- ReactDOM.unmountComponentAtNode(container);
469
+ root.unmount();
470
471
called = 0;
464
- input = ReactDOM.render(<input type={type} onChange={cb} />, container);
465
- // Should be ignored (no change):
466
- input.dispatchEvent(
467
- new Event('input', {bubbles: true, cancelable: true}),
468
- );
469
- setUntrackedValue.call(input, '42');
470
- input.dispatchEvent(
471
- new Event('input', {bubbles: true, cancelable: true}),
472
- );
473
- // Should be ignored (no change):
474
- input.dispatchEvent(
475
- new Event('input', {bubbles: true, cancelable: true}),
476
- );
472
+ root = ReactDOMClient.createRoot(container);
473
+ ref = {current: null};
474
+ await act(() => {
475
+ root.render(<input ref={ref} type={type} onChange={cb} />);
476
+ });
477
+ input = ref.current;
478
+ await act(() => {
479
+ // Should be ignored (no change):
480
+ input.dispatchEvent(
481
+ new Event('input', {bubbles: true, cancelable: true}),
482
+ );
483
+ setUntrackedValue.call(input, '42');
484
+ input.dispatchEvent(
485
+ new Event('input', {bubbles: true, cancelable: true}),
486
+ );
487
+ // Should be ignored (no change):
488
+ input.dispatchEvent(
489
+ new Event('input', {bubbles: true, cancelable: true}),
490
+ );
491
+ });
492
expect(called).toBe(1);
478
- ReactDOM.unmountComponentAtNode(container);
493
+ root.unmount();
494
495
called = 0;
481
- input = ReactDOM.render(<input type={type} onChange={cb} />, container);
482
- // Should be ignored (no change):
483
- input.dispatchEvent(
484
- new Event('change', {bubbles: true, cancelable: true}),
485
- );
486
- setUntrackedValue.call(input, '42');
487
- input.dispatchEvent(
488
- new Event('input', {bubbles: true, cancelable: true}),
489
- );
490
- // Should be ignored (no change):
491
- input.dispatchEvent(
492
- new Event('change', {bubbles: true, cancelable: true}),
493
- );
496
+ root = ReactDOMClient.createRoot(container);
497
+ ref = {current: null};
498
+ await act(() => {
499
+ root.render(<input ref={ref} type={type} onChange={cb} />);
500
+ });
501
+ input = ref.current;
502
+ await act(() => {
503
+ // Should be ignored (no change):
504
+ input.dispatchEvent(
505
+ new Event('change', {bubbles: true, cancelable: true}),
506
+ );
507
+ setUntrackedValue.call(input, '42');
508
+ input.dispatchEvent(
509
+ new Event('input', {bubbles: true, cancelable: true}),
510
+ );
511
+ // Should be ignored (no change):
512
+ input.dispatchEvent(
513
+ new Event('change', {bubbles: true, cancelable: true}),
514
+ );
515
+ });
516
expect(called).toBe(1);
495
- ReactDOM.unmountComponentAtNode(container);
496
- });
517
+ root.unmount();
518
+ }
519
});
520
521
it('should listen for both change and input events when supported', async () => {
packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js
+12
-8
@@ -158,7 +158,8 @@ describe('EnterLeaveEventPlugin', () => {
158
});
159
160
// Test for https://github.com/facebook/react/issues/16763.
161
- it('should call mouseEnter once from sibling rendered inside a rendered component in legacy roots', done => {
161
+ // @gate !disableLegacyMode
162
+ it('should call mouseEnter once from sibling rendered inside a rendered component in legacy roots', async () => {
163
const mockFn = jest.fn();
164
165
class Parent extends React.Component {
@@ -191,8 +192,6 @@ describe('EnterLeaveEventPlugin', () => {
192
relatedTarget: this.firstEl.current,
193
}),
194
);
194
- expect(mockFn.mock.calls.length).toBe(1);
195
- done();
195
}
196
197
render() {
@@ -205,10 +204,14 @@ describe('EnterLeaveEventPlugin', () => {
204
}
205
}
206
208
- ReactDOM.render(<Parent />, container);
207
+ await act(() => {
208
+ ReactDOM.render(<Parent />, container);
209
+ });
210
+ expect(mockFn.mock.calls.length).toBe(1);
211
});
212
211
- it('should call mouseEnter when pressing a non tracked React node in legacy root', done => {
213
+ // @gate !disableLegacyMode
214
+ it('should call mouseEnter when pressing a non tracked React node in legacy root', async () => {
215
const mockFn = jest.fn();
216
217
class Parent extends React.Component {
@@ -243,8 +246,6 @@ describe('EnterLeaveEventPlugin', () => {
246
relatedTarget: this.siblingEl.current,
247
}),
248
);
246
- expect(mockFn.mock.calls.length).toBe(1);
247
- done();
249
}
250
251
render() {
@@ -256,7 +257,10 @@ describe('EnterLeaveEventPlugin', () => {
257
}
258
}
259
259
- ReactDOM.render(<Parent />, container);
260
+ await act(() => {
261
+ ReactDOM.render(<Parent />, container);
262
+ });
263
+ expect(mockFn.mock.calls.length).toBe(1);
264
});
265
266
it('should work with portals outside of the root that has onMouseLeave', async () => {
packages/react-dom/unstable_testing.experimental.js
-4
@@ -11,11 +11,7 @@ export {
11
createPortal,
12
findDOMNode,
13
flushSync,
14
- hydrate,
15
- render,
16
- unmountComponentAtNode,
14
unstable_batchedUpdates,
18
- unstable_renderSubtreeIntoContainer,
15
useFormStatus,
16
useFormState,
17
prefetchDNS,
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+17
@@ -399,6 +399,7 @@ describe('ReactSuspense', () => {
399
},
400
);
401
402
+ // @gate !disableLegacyMode
403
it('mounts a lazy class component in non-concurrent mode (legacy)', async () => {
404
class Class extends React.Component {
405
componentDidMount() {
@@ -685,6 +686,7 @@ describe('ReactSuspense', () => {
686
});
687
688
describe('outside concurrent mode (legacy)', () => {
689
+ // @gate !disableLegacyMode
690
it('a mounted class component can suspend without losing state', async () => {
691
class TextWithLifecycle extends React.Component {
692
componentDidMount() {
@@ -763,6 +765,7 @@ describe('ReactSuspense', () => {
765
expect(container.textContent).toEqual('AB:2C');
766
});
767
768
+ // @gate !disableLegacyMode
769
it('bails out on timed-out primary children even if they receive an update', async () => {
770
let instance;
771
class Stateful extends React.Component {
@@ -803,6 +806,7 @@ describe('ReactSuspense', () => {
806
expect(container.textContent).toEqual('Stateful: 2B');
807
});
808
809
+ // @gate !disableLegacyMode
810
it('when updating a timed-out tree, always retries the suspended component', async () => {
811
let instance;
812
class Stateful extends React.Component {
@@ -858,6 +862,7 @@ describe('ReactSuspense', () => {
862
expect(container.textContent).toEqual('Stateful: 2B');
863
});
864
865
+ // @gate !disableLegacyMode
866
it('suspends in a class that has componentWillUnmount and is then deleted', async () => {
867
class AsyncTextWithUnmount extends React.Component {
868
componentWillUnmount() {
@@ -884,6 +889,7 @@ describe('ReactSuspense', () => {
889
expect(container.textContent).toEqual('B');
890
});
891
892
+ // @gate !disableLegacyMode
893
it('suspends in a component that also contains useEffect', async () => {
894
const {useLayoutEffect} = React;
895
@@ -911,6 +917,7 @@ describe('ReactSuspense', () => {
917
assertLog(['A', 'Did commit: A']);
918
});
919
920
+ // @gate !disableLegacyMode
921
it('retries when an update is scheduled on a timed out tree', async () => {
922
let instance;
923
class Stateful extends React.Component {
@@ -958,6 +965,7 @@ describe('ReactSuspense', () => {
965
expect(container.textContent).toEqual('Step: 3');
966
});
967
968
+ // @gate !disableLegacyMode
969
it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
970
let mounts = 0;
971
class ShouldMountOnce extends React.Component {
@@ -1004,6 +1012,7 @@ describe('ReactSuspense', () => {
1012
expect(mounts).toBe(1);
1013
});
1014
1015
+ // @gate !disableLegacyMode
1016
it('reuses effects, including deletions, from the suspended tree', async () => {
1017
const {useState} = React;
1018
@@ -1045,6 +1054,7 @@ describe('ReactSuspense', () => {
1054
expect(container.textContent).toEqual('Tab: 2 + sibling');
1055
});
1056
1057
+ // @gate !disableLegacyMode
1058
it('does not warn if a mounted component is pinged', async () => {
1059
const {useState} = React;
1060
@@ -1076,6 +1086,7 @@ describe('ReactSuspense', () => {
1086
expect(container.textContent).toEqual('Loading...');
1087
});
1088
1089
+ // @gate !disableLegacyMode
1090
it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
1091
function App() {
1092
return (
@@ -1118,6 +1129,7 @@ describe('ReactSuspense', () => {
1129
]);
1130
});
1131
1132
+ // @gate !disableLegacyMode
1133
it('#14162', async () => {
1134
const {lazy} = React;
1135
@@ -1155,6 +1167,7 @@ describe('ReactSuspense', () => {
1167
ReactDOM.render(<App name="world" />, container);
1168
});
1169
1170
+ // @gate !disableLegacyMode
1171
it('updates memoized child of suspense component when context updates (simple memo)', async () => {
1172
const {useContext, createContext, useState, memo} = React;
1173
@@ -1194,6 +1207,7 @@ describe('ReactSuspense', () => {
1207
expect(container.textContent).toEqual('new value');
1208
});
1209
1210
+ // @gate !disableLegacyMode
1211
it('updates memoized child of suspense component when context updates (manual memo)', async () => {
1212
const {useContext, createContext, useState, memo} = React;
1213
@@ -1238,6 +1252,7 @@ describe('ReactSuspense', () => {
1252
expect(container.textContent).toEqual('new value');
1253
});
1254
1255
+ // @gate !disableLegacyMode
1256
it('updates memoized child of suspense component when context updates (function)', async () => {
1257
const {useContext, createContext, useState} = React;
1258
@@ -1282,6 +1297,7 @@ describe('ReactSuspense', () => {
1297
expect(container.textContent).toEqual('new value');
1298
});
1299
1300
+ // @gate !disableLegacyMode
1301
it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
1302
const {forwardRef, useContext, createContext, useState} = React;
1303
@@ -1321,6 +1337,7 @@ describe('ReactSuspense', () => {
1337
expect(container.textContent).toEqual('new value');
1338
});
1339
1340
+ // @gate !disableLegacyMode
1341
it('updates context consumer within child of suspended suspense component when context updates', async () => {
1342
const {createContext, useState} = React;
1343
packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
+1
@@ -448,6 +448,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
448
expect(container.innerHTML).toBe('<h1>Hello</h1>');
449
});
450
451
+ // @gate !disableLegacyMode
452
it('regression: unmount hidden tree, in legacy mode', async () => {
453
// In legacy mode, when a tree suspends and switches to a fallback, the
454
// effects are not unmounted. So we have to unmount them during a deletion.
packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
+3
@@ -262,6 +262,9 @@ describe('updaters', () => {
262
await waitForAll([]);
263
});
264
265
+ // This test should be convertable to createRoot but the allScheduledTypes assertions are no longer the same
266
+ // So I'm leaving it in legacy mode for now and just disabling if legacy mode is turned off
267
+ // @gate !disableLegacyMode
268
it('should cover suspense pings', async () => {
269
let data = null;
270
let resolver = null;
packages/react/src/__tests__/ReactStrictMode-test.js
+2
@@ -77,6 +77,7 @@ describe('ReactStrictMode', () => {
77
});
78
79
// @gate __DEV__
80
+ // @gate !disableLegacyMode
81
it('should invoke only precommit lifecycle methods twice in legacy roots', async () => {
82
let log = [];
83
let shouldComponentUpdate = false;
@@ -242,6 +243,7 @@ describe('ReactStrictMode', () => {
243
]);
244
});
245
246
+ // @gate !disableLegacyMode
247
it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
248
const {StrictMode} = React;
249
packages/shared/ReactFeatureFlags.js
+5
@@ -195,6 +195,11 @@ export const disableStringRefs = __NEXT_MAJOR__;
195
// Warn on any usage of ReactTestRenderer
196
export const enableReactTestRendererWarning = false;
197
198
+// Disables legacy mode
199
+// This allows us to land breaking changes to remove legacy mode APIs in experimental builds
200
+// before removing them in stable in the next Major
201
+export const disableLegacyMode = __NEXT_MAJOR__;
202
+
203
// -----------------------------------------------------------------------------
204
// Chopping Block
205
//
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -104,6 +104,7 @@ export const enableRefAsProp = false;
104
export const disableStringRefs = false;
105
106
export const enableReactTestRendererWarning = false;
107
+export const disableLegacyMode = false;
108
109
export const enableBigIntSupport = false;
110
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -97,6 +97,7 @@ export const disableStringRefs = false;
97
export const enableReactTestRendererWarning = false;
98
99
export const enableBigIntSupport = false;
100
+export const disableLegacyMode = false;
101
102
// Flow magic to verify the exports of this file match the original version.
103
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -101,6 +101,7 @@ export const enableRefAsProp = __NEXT_MAJOR__;
101
export const disableStringRefs = __NEXT_MAJOR__;
102
export const enableReactTestRendererWarning = false;
103
export const enableBigIntSupport = __NEXT_MAJOR__;
104
+export const disableLegacyMode = __NEXT_MAJOR__;
105
106
// Flow magic to verify the exports of this file match the original version.
107
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -91,6 +91,7 @@ export const enableRefAsProp = false;
91
export const disableStringRefs = false;
92
93
export const enableReactTestRendererWarning = false;
94
+export const disableLegacyMode = false;
95
96
export const enableBigIntSupport = false;
97
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -94,6 +94,7 @@ export const enableRefAsProp = false;
94
export const disableStringRefs = false;
95
96
export const enableReactTestRendererWarning = false;
97
+export const disableLegacyMode = false;
98
99
export const enableBigIntSupport = false;
100
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -126,5 +126,7 @@ export const enableBigIntSupport = false;
126
// because JSX is an extremely hot path.
127
export const disableStringRefs = false;
128
129
+export const disableLegacyMode = false;
130
+
131
// Flow magic to verify the exports of this file match the original version.
132
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
scripts/error-codes/codes.json
+2
-1
@@ -493,5 +493,6 @@
493
"505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain.",
494
"506": "Functions are not valid as a child of Client Components. This may happen if you return %s instead of <%s /> from render. Or maybe you meant to call this function rather than return it.%s",
495
"507": "Expected the last optional `callback` argument to be a function. Instead received: %s.",
496
- "508": "The first argument must be a React class instance. Instead received: %s."
496
+ "508": "The first argument must be a React class instance. Instead received: %s.",
497
+ "509": "ReactDOM: Unsupported Legacy Mode API."
498
}