Skip to content

Commit

Permalink
Deprecate ReactDOM.render and ReactDOM.hydrate (#21652)
Browse files Browse the repository at this point in the history
* 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: reactjs/react.dev#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.
  • Loading branch information
acdlite committed Jun 9, 2021
1 parent 1a3f1af commit aecb3b6
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 43 deletions.
12 changes: 9 additions & 3 deletions fixtures/dom/src/__tests__/wrong-act-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,15 @@ it('warns when using the wrong act version - test + dom: render', () => {
TestRenderer.act(() => {
ReactDOM.render(<App />, document.createElement('div'));
});
}).toWarnDev(["It looks like you're using the wrong act()"], {
withoutStack: true,
});
}).toWarnDev(
[
'ReactDOM.render is no longer supported in React 18.',
"It looks like you're using the wrong act()",
],
{
withoutStack: true,
}
);
});

it('warns when using the wrong act version - test + dom: updates', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,8 @@ describe('InspectedElement', () => {
});

const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(<Target a={1} b="abc" />, container),
);
const root = ReactDOM.createRoot(container);
await utils.actAsync(() => root.render(<Target a={1} b="abc" />));

expect(targetRenderCount).toBe(1);
expect(console.error).toHaveBeenCalledTimes(1);
Expand Down
10 changes: 8 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,16 +787,22 @@ describe('ReactErrorBoundaries', () => {

it('logs a single error when using error boundary', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<ErrorBoundary>
<BrokenRender />
</ErrorBoundary>,
container,
),
).toErrorDev('The above error occurred in the <BrokenRender> component:', {
logAllErrors: true,
});
spyOnDev(console, 'error');
ReactDOM.render(
<ErrorBoundary>
<BrokenRender />
</ErrorBoundary>,
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 <BrokenRender> component:',
);
}

expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
expect(Scheduler).toHaveYielded([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ describe('ReactErrorLoggingRecovery', () => {

beforeEach(() => {
console.error = error => {
if (
typeof error === 'string' &&
error.includes('ReactDOM.render is no longer supported in React 18')
) {
// Ignore legacy root deprecation warning
return;
}
throw new Error('Buggy console.error');
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,16 +668,22 @@ describe('ReactLegacyErrorBoundaries', () => {

it('logs a single error using both error boundaries', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<BothErrorBoundaries>
<BrokenRender />
</BothErrorBoundaries>,
container,
),
).toErrorDev('The above error occurred in the <BrokenRender> component', {
logAllErrors: true,
});
spyOnDev(console, 'error');
ReactDOM.render(
<BothErrorBoundaries>
<BrokenRender />
</BothErrorBoundaries>,
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 <BrokenRender> component:',
);
}

expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
expect(log).toEqual([
Expand Down
38 changes: 38 additions & 0 deletions packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js
Original file line number Diff line number Diff line change
@@ -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',
);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

const stream = require('stream');
const shouldIgnoreConsoleError = require('../../../../../scripts/jest/shouldIgnoreConsoleError');

module.exports = function(initModules) {
let ReactDOM;
Expand Down Expand Up @@ -74,23 +75,29 @@ module.exports = function(initModules) {
}

const result = await fn();
if (
console.error.calls &&
console.error.calls.count() !== count &&
console.error.calls.count() !== 0
) {
console.log(
`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`,
);
if (console.error.calls.count() > 0) {
console.log(`We saw these warnings:`);
for (let i = 0; i < console.error.calls.count(); i++) {
console.log(...console.error.calls.argsFor(i));
if (console.error.calls && console.error.calls.count() !== 0) {
const filteredWarnings = [];
for (let i = 0; i < console.error.calls.count(); i++) {
const args = console.error.calls.argsFor(i);
const [format, ...rest] = args;
if (!shouldIgnoreConsoleError(format, rest)) {
filteredWarnings.push(args);
}
}
if (filteredWarnings.length !== count) {
console.log(
`We expected ${count} warning(s), but saw ${filteredWarnings.length} warning(s).`,
);
if (filteredWarnings.count > 0) {
console.log(`We saw these warnings:`);
for (let i = 0; i < filteredWarnings.length; i++) {
console.log(...filteredWarnings[i]);
}
}
if (__DEV__) {
expect(console.error).toHaveBeenCalledTimes(count);
}
}
}
if (__DEV__) {
expect(console.error).toHaveBeenCalledTimes(count);
}
return result;
}
Expand Down
18 changes: 18 additions & 0 deletions packages/react-dom/src/client/ReactDOMLegacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down Expand Up @@ -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.',
Expand Down
10 changes: 10 additions & 0 deletions scripts/jest/shouldIgnoreConsoleError.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down

0 comments on commit aecb3b6

Please sign in to comment.