Skip to content

Commit 1662035

Browse files
authored
Ensure createRoot warning parity with ReactDOM.render (#17937)
1 parent b2382a7 commit 1662035

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

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

+30
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,34 @@ describe('ReactDOMRoot', () => {
258258
Scheduler.unstable_flushAll();
259259
ReactDOM.createRoot(container); // No warning
260260
});
261+
262+
it('warns if creating a root on the document.body', async () => {
263+
expect(() => {
264+
ReactDOM.createRoot(document.body);
265+
}).toErrorDev(
266+
'createRoot(): Creating roots directly with document.body is ' +
267+
'discouraged, since its children are often manipulated by third-party ' +
268+
'scripts and browser extensions. This may lead to subtle ' +
269+
'reconciliation issues. Try using a container element created ' +
270+
'for your app.',
271+
{withoutStack: true},
272+
);
273+
});
274+
275+
it('warns if updating a root that has had its contents removed', async () => {
276+
const root = ReactDOM.createRoot(container);
277+
root.render(<div>Hi</div>);
278+
Scheduler.unstable_flushAll();
279+
container.innerHTML = '';
280+
281+
expect(() => {
282+
root.render(<div>Hi</div>);
283+
}).toErrorDev(
284+
'render(...): It looks like the React-rendered content of the ' +
285+
'root container was removed without using React. This is not ' +
286+
'supported and will cause errors. Instead, call ' +
287+
"root.unmount() to empty a root's container.",
288+
{withoutStack: true},
289+
);
290+
});
261291
});

packages/react-dom/src/client/ReactDOMRoot.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
1313
// TODO: This type is shared between the reconciler and ReactDOM, but will
1414
// eventually be lifted out to the renderer.
1515
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
16+
import {findHostInstanceWithNoPortals} from 'react-reconciler/inline.dom';
1617

1718
export type RootType = {
1819
render(children: ReactNodeList): void,
@@ -63,15 +64,30 @@ function ReactDOMBlockingRoot(
6364
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
6465
children: ReactNodeList,
6566
): void {
67+
const root = this._internalRoot;
6668
if (__DEV__) {
6769
if (typeof arguments[1] === 'function') {
6870
console.error(
6971
'render(...): does not support the second callback argument. ' +
7072
'To execute a side effect after rendering, declare it in a component body with useEffect().',
7173
);
7274
}
75+
const container = root.containerInfo;
76+
77+
if (container.nodeType !== COMMENT_NODE) {
78+
const hostInstance = findHostInstanceWithNoPortals(root.current);
79+
if (hostInstance) {
80+
if (hostInstance.parentNode !== container) {
81+
console.error(
82+
'render(...): It looks like the React-rendered content of the ' +
83+
'root container was removed without using React. This is not ' +
84+
'supported and will cause errors. Instead, call ' +
85+
"root.unmount() to empty a root's container.",
86+
);
87+
}
88+
}
89+
}
7390
}
74-
const root = this._internalRoot;
7591
updateContainer(children, root, null, null);
7692
};
7793

@@ -156,6 +172,19 @@ export function isValidContainer(node: mixed): boolean {
156172

157173
function warnIfReactDOMContainerInDEV(container) {
158174
if (__DEV__) {
175+
if (
176+
container.nodeType === ELEMENT_NODE &&
177+
((container: any): Element).tagName &&
178+
((container: any): Element).tagName.toUpperCase() === 'BODY'
179+
) {
180+
console.error(
181+
'createRoot(): Creating roots directly with document.body is ' +
182+
'discouraged, since its children are often manipulated by third-party ' +
183+
'scripts and browser extensions. This may lead to subtle ' +
184+
'reconciliation issues. Try using a container element created ' +
185+
'for your app.',
186+
);
187+
}
159188
if (isContainerMarkedAsRoot(container)) {
160189
if (container._reactRootContainer) {
161190
console.error(

0 commit comments

Comments
 (0)