diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
index a6bd6c3a330d8..0d842481b0180 100644
--- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
@@ -258,4 +258,34 @@ describe('ReactDOMRoot', () => {
Scheduler.unstable_flushAll();
ReactDOM.createRoot(container); // No warning
});
+
+ it('warns if creating a root on the document.body', async () => {
+ expect(() => {
+ ReactDOM.createRoot(document.body);
+ }).toErrorDev(
+ 'createRoot(): Creating roots directly with document.body is ' +
+ 'discouraged, since its children are often manipulated by third-party ' +
+ 'scripts and browser extensions. This may lead to subtle ' +
+ 'reconciliation issues. Try using a container element created ' +
+ 'for your app.',
+ {withoutStack: true},
+ );
+ });
+
+ it('warns if updating a root that has had its contents removed', async () => {
+ const root = ReactDOM.createRoot(container);
+ root.render(
Hi
);
+ Scheduler.unstable_flushAll();
+ container.innerHTML = '';
+
+ expect(() => {
+ root.render(Hi
);
+ }).toErrorDev(
+ 'render(...): It looks like the React-rendered content of the ' +
+ 'root container was removed without using React. This is not ' +
+ 'supported and will cause errors. Instead, call ' +
+ "root.unmount() to empty a root's container.",
+ {withoutStack: true},
+ );
+ });
});
diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js
index 62834dbde2ab9..f9282e919c0dd 100644
--- a/packages/react-dom/src/client/ReactDOMRoot.js
+++ b/packages/react-dom/src/client/ReactDOMRoot.js
@@ -13,6 +13,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
// TODO: This type is shared between the reconciler and ReactDOM, but will
// eventually be lifted out to the renderer.
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
+import {findHostInstanceWithNoPortals} from 'react-reconciler/inline.dom';
export type RootType = {
render(children: ReactNodeList): void,
@@ -63,6 +64,7 @@ function ReactDOMBlockingRoot(
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
children: ReactNodeList,
): void {
+ const root = this._internalRoot;
if (__DEV__) {
if (typeof arguments[1] === 'function') {
console.error(
@@ -70,8 +72,22 @@ ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function
'To execute a side effect after rendering, declare it in a component body with useEffect().',
);
}
+ const container = root.containerInfo;
+
+ if (container.nodeType !== COMMENT_NODE) {
+ const hostInstance = findHostInstanceWithNoPortals(root.current);
+ if (hostInstance) {
+ if (hostInstance.parentNode !== container) {
+ console.error(
+ 'render(...): It looks like the React-rendered content of the ' +
+ 'root container was removed without using React. This is not ' +
+ 'supported and will cause errors. Instead, call ' +
+ "root.unmount() to empty a root's container.",
+ );
+ }
+ }
+ }
}
- const root = this._internalRoot;
updateContainer(children, root, null, null);
};
@@ -156,6 +172,19 @@ export function isValidContainer(node: mixed): boolean {
function warnIfReactDOMContainerInDEV(container) {
if (__DEV__) {
+ if (
+ container.nodeType === ELEMENT_NODE &&
+ ((container: any): Element).tagName &&
+ ((container: any): Element).tagName.toUpperCase() === 'BODY'
+ ) {
+ console.error(
+ 'createRoot(): Creating roots directly with document.body is ' +
+ 'discouraged, since its children are often manipulated by third-party ' +
+ 'scripts and browser extensions. This may lead to subtle ' +
+ 'reconciliation issues. Try using a container element created ' +
+ 'for your app.',
+ );
+ }
if (isContainerMarkedAsRoot(container)) {
if (container._reactRootContainer) {
console.error(