Skip to content

Commit 789d1cf

Browse files
committed
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 01ab35a commit 789d1cf

File tree

44 files changed

+484
-149
lines changed

Some content is hidden

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

44 files changed

+484
-149
lines changed

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
@@ -452,6 +453,7 @@ describe('ReactComponent', () => {
452453
/* eslint-enable indent */
453454
});
454455

456+
// @gate !disableLegacyMode
455457
it('fires the callback after a component is rendered in legacy roots', () => {
456458
const callback = jest.fn();
457459
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

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

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ describe('ReactDOMInput', () => {
733733
expect(node.value).toBe('foobar');
734734
});
735735

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 () => {
737737
class TemporalLike {
738738
valueOf() {
739739
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -744,19 +744,16 @@ describe('ReactDOMInput', () => {
744744
return '2020-01-01';
745745
}
746746
}
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.',
753755
);
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'));
760757
});
761758

762759
it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
@@ -1736,6 +1733,7 @@ describe('ReactDOMInput', () => {
17361733
assertInputTrackingIsCurrent(container);
17371734
});
17381735

1736+
// @gate !disableLegacyMode
17391737
it('should control radio buttons if the tree updates during render in legacy mode', async () => {
17401738
container.remove();
17411739
container = document.createElement('div');

packages/react-dom/src/__tests__/ReactDOMLegacyComponentTree-test.internal.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('ReactDOMComponentTree', () => {
2727
container = null;
2828
});
2929

30+
// @gate !disableLegacyMode
3031
it('finds instance of node that is attempted to be unmounted', () => {
3132
const component = <div />;
3233
const node = ReactDOM.render(<div>{component}</div>, container);
@@ -39,6 +40,7 @@ describe('ReactDOMComponentTree', () => {
3940
);
4041
});
4142

43+
// @gate !disableLegacyMode
4244
it('finds instance from node to stop rendering over other react rendered components', () => {
4345
const component = (
4446
<div>

0 commit comments

Comments
 (0)