Skip to content

Commit 1c02b9d

Browse files
authored
[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.
1 parent 034130c commit 1c02b9d

File tree

46 files changed

+483
-187
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+483
-187
lines changed

packages/react-devtools-shared/src/__tests__/store-test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ describe('Store', () => {
227227

228228
// @reactVersion >= 18.0
229229
// @reactVersion < 19
230+
// @gate !disableLegacyMode
230231
it('should support mount and update operations for multiple roots (legacy render)', () => {
231232
const Parent = ({count}) =>
232233
new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -941,6 +942,7 @@ describe('Store', () => {
941942

942943
// @reactVersion >= 18.0
943944
// @reactVersion < 19
945+
// @gate !disableLegacyMode
944946
it('should support mount and update operations for multiple roots (legacy render)', () => {
945947
const Parent = ({count}) =>
946948
new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -1469,6 +1471,7 @@ describe('Store', () => {
14691471

14701472
// @reactVersion >= 18.0
14711473
// @reactVersion < 19
1474+
// @gate !disableLegacyMode
14721475
it('detects and updates profiling support based on the attached roots (legacy render)', () => {
14731476
const Component = () => null;
14741477

@@ -1632,6 +1635,7 @@ describe('Store', () => {
16321635

16331636
// @reactVersion >= 18.0
16341637
// @reactVersion < 19
1638+
// @gate !disableLegacyMode
16351639
it('should support Lazy components (legacy render)', async () => {
16361640
const container = document.createElement('div');
16371641

@@ -1702,6 +1706,7 @@ describe('Store', () => {
17021706

17031707
// @reactVersion >= 18.0
17041708
// @reactVersion < 19
1709+
// @gate !disableLegacyMode
17051710
it('should support Lazy components that are unmounted before they finish loading (legacy render)', async () => {
17061711
const container = document.createElement('div');
17071712

packages/react-devtools-shared/src/__tests__/storeStressSync-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ describe('StoreStress (Legacy Mode)', () => {
3636
// It renders different trees that should produce the same output.
3737
// @reactVersion >= 16.9
3838
// @reactVersion < 19
39+
// @gate !disableLegacyMode
3940
it('should handle a stress test with different tree operations (Legacy Mode)', () => {
4041
let setShowX;
4142
const A = () => 'a';

packages/react-dom/index.experimental.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ export {
1414
hydrateRoot,
1515
findDOMNode,
1616
flushSync,
17-
hydrate,
18-
render,
19-
unmountComponentAtNode,
2017
unstable_batchedUpdates,
21-
unstable_renderSubtreeIntoContainer,
2218
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
2319
useFormStatus,
2420
useFormState,

packages/react-dom/src/__tests__/ReactComponent-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('ReactComponent', () => {
2626
act = require('internal-test-utils').act;
2727
});
2828

29+
// @gate !disableLegacyMode
2930
it('should throw on invalid render targets in legacy roots', () => {
3031
const container = document.createElement('div');
3132
// jQuery objects are basically arrays; people often pass them in by mistake
@@ -455,6 +456,7 @@ describe('ReactComponent', () => {
455456
/* eslint-enable indent */
456457
});
457458

459+
// @gate !disableLegacyMode
458460
it('fires the callback after a component is rendered in legacy roots', () => {
459461
const callback = jest.fn();
460462
const container = document.createElement('div');

packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ describe('ReactCompositeComponent-state', () => {
630630
);
631631
});
632632

633+
// @gate !disableLegacyMode
633634
it('Legacy mode should support setState in componentWillUnmount (#18851)', () => {
634635
let subscription;
635636
class A extends React.Component {

packages/react-dom/src/__tests__/ReactDOM-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ describe('ReactDOM', () => {
164164
expect(dog.className).toBe('bigdog');
165165
});
166166

167+
// @gate !disableLegacyMode
167168
it('throws in render() if the mount callback in legacy roots is not a function', async () => {
168169
function Foo() {
169170
this.a = 1;
@@ -216,6 +217,7 @@ describe('ReactDOM', () => {
216217
);
217218
});
218219

220+
// @gate !disableLegacyMode
219221
it('throws in render() if the update callback in legacy roots is not a function', async () => {
220222
function Foo() {
221223
this.a = 1;

packages/react-dom/src/__tests__/ReactDOMComponent-test.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ describe('ReactDOMComponent', () => {
332332
});
333333
});
334334

335-
it('throws with Temporal-like objects as style values', () => {
335+
it('throws with Temporal-like objects as style values', async () => {
336336
class TemporalLike {
337337
valueOf() {
338338
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -344,14 +344,17 @@ describe('ReactDOMComponent', () => {
344344
}
345345
}
346346
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'));
355358
});
356359

357360
it('should update styles if initially null', async () => {
@@ -3688,6 +3691,7 @@ describe('ReactDOMComponent', () => {
36883691
expect(typeof portalContainer.onclick).toBe('function');
36893692
});
36903693

3694+
// @gate !disableLegacyMode
36913695
it('does not add onclick handler to the React root in legacy mode', () => {
36923696
const container = document.createElement('div');
36933697

packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
5555
});
5656

5757
describe('ReactDOM.render', () => {
58+
// @gate !disableLegacyMode
5859
it('logs errors during event handlers', async () => {
5960
spyOnDevAndProd(console, 'error');
6061

@@ -156,6 +157,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
156157
}
157158
});
158159

160+
// @gate !disableLegacyMode
159161
it('logs render errors without an error boundary', async () => {
160162
spyOnDevAndProd(console, 'error');
161163

@@ -223,6 +225,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
223225
}
224226
});
225227

228+
// @gate !disableLegacyMode
226229
it('logs render errors with an error boundary', async () => {
227230
spyOnDevAndProd(console, 'error');
228231

@@ -295,6 +298,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
295298
}
296299
});
297300

301+
// @gate !disableLegacyMode
298302
it('logs layout effect errors without an error boundary', async () => {
299303
spyOnDevAndProd(console, 'error');
300304

@@ -365,6 +369,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
365369
}
366370
});
367371

372+
// @gate !disableLegacyMode
368373
it('logs layout effect errors with an error boundary', async () => {
369374
spyOnDevAndProd(console, 'error');
370375

@@ -440,6 +445,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
440445
}
441446
});
442447

448+
// @gate !disableLegacyMode
443449
it('logs passive effect errors without an error boundary', async () => {
444450
spyOnDevAndProd(console, 'error');
445451

@@ -511,6 +517,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
511517
}
512518
});
513519

520+
// @gate !disableLegacyMode
514521
it('logs passive effect errors with an error boundary', async () => {
515522
spyOnDevAndProd(console, 'error');
516523

packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('ReactDOMFiberAsync', () => {
5050
document.body.removeChild(container);
5151
});
5252

53+
// @gate !disableLegacyMode
5354
it('renders synchronously by default in legacy mode', () => {
5455
const ops = [];
5556
ReactDOM.render(<div>Hi</div>, container, () => {

packages/react-dom/src/__tests__/ReactDOMHooks-test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe('ReactDOMHooks', () => {
3535
document.body.removeChild(container);
3636
});
3737

38+
// @gate !disableLegacyMode
3839
it('can ReactDOM.render() from useEffect', async () => {
3940
const container2 = document.createElement('div');
4041
const container3 = document.createElement('div');
@@ -76,6 +77,50 @@ describe('ReactDOMHooks', () => {
7677
expect(container3.textContent).toBe('6');
7778
});
7879

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
79124
it('should not bail out when an update is scheduled from within an event handler', () => {
80125
const {createRef, useCallback, useState} = React;
81126

0 commit comments

Comments
 (0)