From 6d8823d817bb93ba9cb2ee34b248d8c42f944c4e Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 9 Jun 2021 16:10:51 -0400 Subject: [PATCH] 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. --- .../src/__tests__/ReactDOMFiber-test.js | 10 ++++- .../ReactErrorBoundaries-test.internal.js | 26 ++++++++----- .../ReactErrorLoggingRecovery-test.js | 6 +++ ...eactLegacyErrorBoundaries-test.internal.js | 26 ++++++++----- .../__tests__/ReactLegacyRootWarnings-test.js | 38 +++++++++++++++++++ .../react-dom/src/client/ReactDOMLegacy.js | 18 +++++++++ scripts/jest/shouldIgnoreConsoleError.js | 10 +++++ 7 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index fefcb2b08008d..36c8367af5d48 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -1154,9 +1154,15 @@ describe('ReactDOMFiber', () => { expect(ops).toEqual(['A']); if (__DEV__) { - // TODO: this warning shouldn't be firing in the first place if user didn't call it. const errorCalls = console.error.calls.count(); - for (let i = 0; i < errorCalls; i++) { + expect(console.error.calls.argsFor(0)[0]).toMatch( + 'ReactDOM.render is no longer supported in React 18', + ); + expect(console.error.calls.argsFor(1)[0]).toMatch( + 'ReactDOM.render is no longer supported in React 18', + ); + // TODO: this warning shouldn't be firing in the first place if user didn't call it. + for (let i = 2; i < errorCalls; i++) { expect(console.error.calls.argsFor(i)[0]).toMatch( 'unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.', ); diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 02e4833685528..85c7a2311e0c2 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -787,16 +787,22 @@ describe('ReactErrorBoundaries', () => { it('logs a single error when using error boundary', () => { const container = document.createElement('div'); - expect(() => - ReactDOM.render( - - - , - container, - ), - ).toErrorDev('The above error occurred in the component:', { - logAllErrors: true, - }); + spyOnDev(console, 'error'); + ReactDOM.render( + + + , + container, + ); + if (__DEV__) { + expect(console.error).toHaveBeenCalledTimes(2); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'ReactDOM.render is no longer supported', + ); + expect(console.error.calls.argsFor(1)[0]).toContain( + 'The above error occurred in the component:', + ); + } expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); expect(Scheduler).toHaveYielded([ diff --git a/packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js b/packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js index 21b868557ef2c..d6815df58efb7 100644 --- a/packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js +++ b/packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js @@ -43,6 +43,12 @@ describe('ReactErrorLoggingRecovery', () => { beforeEach(() => { console.error = error => { + if ( + error.includes('ReactDOM.render is no longer supported in React 18') + ) { + // Ignore legacy root deprecation warning + return; + } throw new Error('Buggy console.error'); }; }); diff --git a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js index b661ead42df3f..92de8b413b163 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js @@ -668,16 +668,22 @@ describe('ReactLegacyErrorBoundaries', () => { it('logs a single error using both error boundaries', () => { const container = document.createElement('div'); - expect(() => - ReactDOM.render( - - - , - container, - ), - ).toErrorDev('The above error occurred in the component', { - logAllErrors: true, - }); + spyOnDev(console, 'error'); + ReactDOM.render( + + + , + container, + ); + if (__DEV__) { + expect(console.error).toHaveBeenCalledTimes(2); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'ReactDOM.render is no longer supported', + ); + expect(console.error.calls.argsFor(1)[0]).toContain( + 'The above error occurred in the component:', + ); + } expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); expect(log).toEqual([ diff --git a/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js b/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js new file mode 100644 index 0000000000000..f8d039124f4e2 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js @@ -0,0 +1,38 @@ +let ReactDOM = require('react-dom'); + +describe('ReactDOMRoot', () => { + let container; + + beforeEach(() => { + jest.resetModules(); + container = document.createElement('div'); + ReactDOM = require('react-dom'); + }); + + test('deprecation warning for ReactDOM.render', () => { + spyOnDev(console, 'error'); + + ReactDOM.render('Hi', container); + expect(container.textContent).toEqual('Hi'); + if (__DEV__) { + expect(console.error).toHaveBeenCalledTimes(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'ReactDOM.render is no longer supported', + ); + } + }); + + test('deprecation warning for ReactDOM.hydrate', () => { + spyOnDev(console, 'error'); + + container.innerHTML = 'Hi'; + ReactDOM.hydrate('Hi', container); + expect(container.textContent).toEqual('Hi'); + if (__DEV__) { + expect(console.error).toHaveBeenCalledTimes(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'ReactDOM.hydrate is no longer supported', + ); + } + }); +}); diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index f1ee572ee6eff..e85a5541b19e6 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -219,6 +219,15 @@ export function hydrate( container: Container, callback: ?Function, ) { + if (__DEV__) { + console.error( + 'ReactDOM.hydrate is no longer supported in React 18. Use createRoot ' + + 'instead. Until you switch to the new API, your app will behave as ' + + "if it's running React 17. Learn " + + 'more: https://reactjs.org/link/switch-to-createroot', + ); + } + invariant( isValidContainer(container), 'Target container is not a DOM element.', @@ -250,6 +259,15 @@ export function render( container: Container, callback: ?Function, ) { + if (__DEV__) { + console.error( + 'ReactDOM.render is no longer supported in React 18. Use createRoot ' + + 'instead. Until you switch to the new API, your app will behave as ' + + "if it's running React 17. Learn " + + 'more: https://reactjs.org/link/switch-to-createroot', + ); + } + invariant( isValidContainer(container), 'Target container is not a DOM element.', diff --git a/scripts/jest/shouldIgnoreConsoleError.js b/scripts/jest/shouldIgnoreConsoleError.js index cae46ac7c8c32..7534b4339abbf 100644 --- a/scripts/jest/shouldIgnoreConsoleError.js +++ b/scripts/jest/shouldIgnoreConsoleError.js @@ -13,6 +13,16 @@ module.exports = function shouldIgnoreConsoleError(format, args) { // Ignore it too. return true; } + if ( + format.indexOf('ReactDOM.render is no longer supported in React 18') !== + -1 || + format.indexOf( + 'ReactDOM.hydrate is no longer supported in React 18' + ) !== -1 + ) { + // We haven't finished migrating our tests to use createRoot. + return true; + } } } else { if (