@samitouri / QOS-React-2 / commits / aecb3b6d11

Deprecate ReactDOM.render and ReactDOM.hydrate (#21652)

* Use existing test warning filter for server tests We have a warning filter for our internal tests to ignore warnings that are too noisy or that we haven't removed from our test suite yet: shouldIgnoreConsoleError. Many of our server rendering tests don't use this filter, though, because it has its own special of asserting warnings. So I added the warning filter to the server tests, too. * Deprecate ReactDOM.render and ReactDOM.hydrate These are no longer supported in React 18. They are replaced by the `createRoot` API. The warning includes a link to documentation of the new API. Currently it redirects to the corresponding working group post. Here's the PR to set up the redirect: https://github.com/reactjs/reactjs.org/pull/3730 Many of our tests still use ReactDOM.render. We will need to gradually migrate them over to createRoot. In the meantime, I added the warnings to our internal warning filter.

Andrew Clark committed Jun 9, 2021 at 16:46 UTC aecb3b6d114e8fafddf6982133737198e8ea7cb3
10 files changed +146 -43
fixtures/dom/src/__tests__/wrong-act-test.js
+9 -3
@@ -101,9 +101,15 @@ it('warns when using the wrong act version - test + dom: render', () => {
101 TestRenderer.act(() => {
102 ReactDOM.render(<App />, document.createElement('div'));
103 });
104 - }).toWarnDev(["It looks like you're using the wrong act()"], {
105 - withoutStack: true,
106 - });
104 + }).toWarnDev(
105 + [
106 + 'ReactDOM.render is no longer supported in React 18.',
107 + "It looks like you're using the wrong act()",
108 + ],
109 + {
110 + withoutStack: true,
111 + }
112 + );
113 });
114
115 it('warns when using the wrong act version - test + dom: updates', () => {
packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
+2 -3
@@ -390,9 +390,8 @@ describe('InspectedElement', () => {
390 });
391
392 const container = document.createElement('div');
393 - await utils.actAsync(() =>
394 - ReactDOM.render(<Target a={1} b="abc" />, container),
395 - );
393 + const root = ReactDOM.createRoot(container);
394 + await utils.actAsync(() => root.render(<Target a={1} b="abc" />));
395
396 expect(targetRenderCount).toBe(1);
397 expect(console.error).toHaveBeenCalledTimes(1);
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+8 -2
@@ -1154,9 +1154,15 @@ describe('ReactDOMFiber', () => {
1154 expect(ops).toEqual(['A']);
1155
1156 if (__DEV__) {
1157 - // TODO: this warning shouldn't be firing in the first place if user didn't call it.
1157 const errorCalls = console.error.calls.count();
1159 - for (let i = 0; i < errorCalls; i++) {
1158 + expect(console.error.calls.argsFor(0)[0]).toMatch(
1159 + 'ReactDOM.render is no longer supported in React 18',
1160 + );
1161 + expect(console.error.calls.argsFor(1)[0]).toMatch(
1162 + 'ReactDOM.render is no longer supported in React 18',
1163 + );
1164 + // TODO: this warning shouldn't be firing in the first place if user didn't call it.
1165 + for (let i = 2; i < errorCalls; i++) {
1166 expect(console.error.calls.argsFor(i)[0]).toMatch(
1167 'unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.',
1168 );
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+16 -10
@@ -787,16 +787,22 @@ describe('ReactErrorBoundaries', () => {
787
788 it('logs a single error when using error boundary', () => {
789 const container = document.createElement('div');
790 - expect(() =>
791 - ReactDOM.render(
792 - <ErrorBoundary>
793 - <BrokenRender />
794 - </ErrorBoundary>,
795 - container,
796 - ),
797 - ).toErrorDev('The above error occurred in the <BrokenRender> component:', {
798 - logAllErrors: true,
799 - });
790 + spyOnDev(console, 'error');
791 + ReactDOM.render(
792 + <ErrorBoundary>
793 + <BrokenRender />
794 + </ErrorBoundary>,
795 + container,
796 + );
797 + if (__DEV__) {
798 + expect(console.error).toHaveBeenCalledTimes(2);
799 + expect(console.error.calls.argsFor(0)[0]).toContain(
800 + 'ReactDOM.render is no longer supported',
801 + );
802 + expect(console.error.calls.argsFor(1)[0]).toContain(
803 + 'The above error occurred in the <BrokenRender> component:',
804 + );
805 + }
806
807 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
808 expect(Scheduler).toHaveYielded([
packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js
+7
@@ -43,6 +43,13 @@ describe('ReactErrorLoggingRecovery', () => {
43
44 beforeEach(() => {
45 console.error = error => {
46 + if (
47 + typeof error === 'string' &&
48 + error.includes('ReactDOM.render is no longer supported in React 18')
49 + ) {
50 + // Ignore legacy root deprecation warning
51 + return;
52 + }
53 throw new Error('Buggy console.error');
54 };
55 });
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+16 -10
@@ -668,16 +668,22 @@ describe('ReactLegacyErrorBoundaries', () => {
668
669 it('logs a single error using both error boundaries', () => {
670 const container = document.createElement('div');
671 - expect(() =>
672 - ReactDOM.render(
673 - <BothErrorBoundaries>
674 - <BrokenRender />
675 - </BothErrorBoundaries>,
676 - container,
677 - ),
678 - ).toErrorDev('The above error occurred in the <BrokenRender> component', {
679 - logAllErrors: true,
680 - });
671 + spyOnDev(console, 'error');
672 + ReactDOM.render(
673 + <BothErrorBoundaries>
674 + <BrokenRender />
675 + </BothErrorBoundaries>,
676 + container,
677 + );
678 + if (__DEV__) {
679 + expect(console.error).toHaveBeenCalledTimes(2);
680 + expect(console.error.calls.argsFor(0)[0]).toContain(
681 + 'ReactDOM.render is no longer supported',
682 + );
683 + expect(console.error.calls.argsFor(1)[0]).toContain(
684 + 'The above error occurred in the <BrokenRender> component:',
685 + );
686 + }
687
688 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
689 expect(log).toEqual([
packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js new
+38
@@ -0,0 +1,38 @@
1 +let ReactDOM = require('react-dom');
2 +
3 +describe('ReactDOMRoot', () => {
4 + let container;
5 +
6 + beforeEach(() => {
7 + jest.resetModules();
8 + container = document.createElement('div');
9 + ReactDOM = require('react-dom');
10 + });
11 +
12 + test('deprecation warning for ReactDOM.render', () => {
13 + spyOnDev(console, 'error');
14 +
15 + ReactDOM.render('Hi', container);
16 + expect(container.textContent).toEqual('Hi');
17 + if (__DEV__) {
18 + expect(console.error).toHaveBeenCalledTimes(1);
19 + expect(console.error.calls.argsFor(0)[0]).toContain(
20 + 'ReactDOM.render is no longer supported',
21 + );
22 + }
23 + });
24 +
25 + test('deprecation warning for ReactDOM.hydrate', () => {
26 + spyOnDev(console, 'error');
27 +
28 + container.innerHTML = 'Hi';
29 + ReactDOM.hydrate('Hi', container);
30 + expect(container.textContent).toEqual('Hi');
31 + if (__DEV__) {
32 + expect(console.error).toHaveBeenCalledTimes(1);
33 + expect(console.error.calls.argsFor(0)[0]).toContain(
34 + 'ReactDOM.hydrate is no longer supported',
35 + );
36 + }
37 + });
38 +});
packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+22 -15
@@ -10,6 +10,7 @@
10 'use strict';
11
12 const stream = require('stream');
13 +const shouldIgnoreConsoleError = require('../../../../../scripts/jest/shouldIgnoreConsoleError');
14
15 module.exports = function(initModules) {
16 let ReactDOM;
@@ -74,23 +75,29 @@ module.exports = function(initModules) {
75 }
76
77 const result = await fn();
77 - if (
78 - console.error.calls &&
79 - console.error.calls.count() !== count &&
80 - console.error.calls.count() !== 0
81 - ) {
82 - console.log(
83 - `We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`,
84 - );
85 - if (console.error.calls.count() > 0) {
86 - console.log(`We saw these warnings:`);
87 - for (let i = 0; i < console.error.calls.count(); i++) {
88 - console.log(...console.error.calls.argsFor(i));
78 + if (console.error.calls && console.error.calls.count() !== 0) {
79 + const filteredWarnings = [];
80 + for (let i = 0; i < console.error.calls.count(); i++) {
81 + const args = console.error.calls.argsFor(i);
82 + const [format, ...rest] = args;
83 + if (!shouldIgnoreConsoleError(format, rest)) {
84 + filteredWarnings.push(args);
85 + }
86 + }
87 + if (filteredWarnings.length !== count) {
88 + console.log(
89 + `We expected ${count} warning(s), but saw ${filteredWarnings.length} warning(s).`,
90 + );
91 + if (filteredWarnings.count > 0) {
92 + console.log(`We saw these warnings:`);
93 + for (let i = 0; i < filteredWarnings.length; i++) {
94 + console.log(...filteredWarnings[i]);
95 + }
96 + }
97 + if (__DEV__) {
98 + expect(console.error).toHaveBeenCalledTimes(count);
99 }
100 }
91 - }
92 - if (__DEV__) {
93 - expect(console.error).toHaveBeenCalledTimes(count);
101 }
102 return result;
103 }
packages/react-dom/src/client/ReactDOMLegacy.js
+18
@@ -219,6 +219,15 @@ export function hydrate(
219 container: Container,
220 callback: ?Function,
221 ) {
222 + if (__DEV__) {
223 + console.error(
224 + 'ReactDOM.hydrate is no longer supported in React 18. Use createRoot ' +
225 + 'instead. Until you switch to the new API, your app will behave as ' +
226 + "if it's running React 17. Learn " +
227 + 'more: https://reactjs.org/link/switch-to-createroot',
228 + );
229 + }
230 +
231 invariant(
232 isValidContainer(container),
233 'Target container is not a DOM element.',
@@ -250,6 +259,15 @@ export function render(
259 container: Container,
260 callback: ?Function,
261 ) {
262 + if (__DEV__) {
263 + console.error(
264 + 'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
265 + 'instead. Until you switch to the new API, your app will behave as ' +
266 + "if it's running React 17. Learn " +
267 + 'more: https://reactjs.org/link/switch-to-createroot',
268 + );
269 + }
270 +
271 invariant(
272 isValidContainer(container),
273 'Target container is not a DOM element.',
scripts/jest/shouldIgnoreConsoleError.js
+10
@@ -13,6 +13,16 @@ module.exports = function shouldIgnoreConsoleError(format, args) {
13 // Ignore it too.
14 return true;
15 }
16 + if (
17 + format.indexOf('ReactDOM.render is no longer supported in React 18') !==
18 + -1 ||
19 + format.indexOf(
20 + 'ReactDOM.hydrate is no longer supported in React 18'
21 + ) !== -1
22 + ) {
23 + // We haven't finished migrating our tests to use createRoot.
24 + return true;
25 + }
26 }
27 } else {
28 if (