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

Ensure createRoot warning parity with ReactDOM.render (#17937)

Dominic Gannaway committed Jan 30, 2020 at 17:17 UTC 1662035852519983955c8c3bdab72a0c60b1264b
2 files changed +60 -1
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+30
@@ -258,4 +258,34 @@ describe('ReactDOMRoot', () => {
258 Scheduler.unstable_flushAll();
259 ReactDOM.createRoot(container); // No warning
260 });
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 + });
291 });
packages/react-dom/src/client/ReactDOMRoot.js
+30 -1
@@ -13,6 +13,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
13 // TODO: This type is shared between the reconciler and ReactDOM, but will
14 // eventually be lifted out to the renderer.
15 import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
16 +import {findHostInstanceWithNoPortals} from 'react-reconciler/inline.dom';
17
18 export type RootType = {
19 render(children: ReactNodeList): void,
@@ -63,6 +64,7 @@ function ReactDOMBlockingRoot(
64 ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
65 children: ReactNodeList,
66 ): void {
67 + const root = this._internalRoot;
68 if (__DEV__) {
69 if (typeof arguments[1] === 'function') {
70 console.error(
@@ -70,8 +72,22 @@ ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function
72 'To execute a side effect after rendering, declare it in a component body with useEffect().',
73 );
74 }
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 + }
90 }
74 - const root = this._internalRoot;
91 updateContainer(children, root, null, null);
92 };
93
@@ -156,6 +172,19 @@ export function isValidContainer(node: mixed): boolean {
172
173 function warnIfReactDOMContainerInDEV(container) {
174 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 + }
188 if (isContainerMarkedAsRoot(container)) {
189 if (container._reactRootContainer) {
190 console.error(