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

Warn when doing createRoot twice on the same node (another approach) (#17329)

* Unify fields used for createRoot warning and event system * Warn when doing createRoot twice on the same node * Stricter check for modern roots * Unmark asynchronously * Fix Flow

Dan Abramov committed Nov 10, 2019 at 00:54 UTC a7b4d51a20a00f0f60b113c3a02326ccf69aef82
3 files changed +90 -31
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+19
@@ -212,4 +212,23 @@ describe('ReactDOMRoot', () => {
212 {withoutStack: true},
213 );
214 });
215 +
216 + it('warns when creating two roots managing the same container', () => {
217 + ReactDOM.createRoot(container);
218 + expect(() => {
219 + ReactDOM.createRoot(container);
220 + }).toWarnDev(
221 + 'You are calling ReactDOM.createRoot() on a container that ' +
222 + 'has already been passed to createRoot() before. Instead, call ' +
223 + 'root.render() on the existing root instead if you want to update it.',
224 + {withoutStack: true},
225 + );
226 + });
227 +
228 + it('does not warn when creating second root after first one is unmounted', () => {
229 + const root = ReactDOM.createRoot(container);
230 + root.unmount();
231 + Scheduler.unstable_flushAll();
232 + ReactDOM.createRoot(container); // No warning
233 + });
234 });
packages/react-dom/src/client/ReactDOM.js
+63 -31
@@ -68,7 +68,9 @@ import {
68 getNodeFromInstance,
69 getFiberCurrentPropsFromNode,
70 getClosestInstanceFromNode,
71 + isContainerMarkedAsRoot,
72 markContainerAsRoot,
73 + unmarkContainerAsRoot,
74 } from './ReactDOMComponentTree';
75 import {restoreControlledState} from './ReactDOMComponent';
76 import {dispatchEvent} from '../events/ReactDOMEventListener';
@@ -174,11 +176,9 @@ setRestoreImplementation(restoreControlledState);
176 export type DOMContainer =
177 | (Element & {
178 _reactRootContainer: ?_ReactRoot,
177 - _reactHasBeenPassedToCreateRootDEV: ?boolean,
179 })
180 | (Document & {
181 _reactRootContainer: ?_ReactRoot,
181 - _reactHasBeenPassedToCreateRootDEV: ?boolean,
182 });
183
184 type _ReactRoot = {
@@ -226,22 +226,28 @@ ReactRoot.prototype.render = ReactBlockingRoot.prototype.render = function(
226 callback: ?() => mixed,
227 ): void {
228 const root = this._internalRoot;
229 - callback = callback === undefined ? null : callback;
229 + const cb = callback === undefined ? null : callback;
230 if (__DEV__) {
231 - warnOnInvalidCallback(callback, 'render');
231 + warnOnInvalidCallback(cb, 'render');
232 }
233 - updateContainer(children, root, null, callback);
233 + updateContainer(children, root, null, cb);
234 };
235
236 ReactRoot.prototype.unmount = ReactBlockingRoot.prototype.unmount = function(
237 callback: ?() => mixed,
238 ): void {
239 const root = this._internalRoot;
240 - callback = callback === undefined ? null : callback;
240 + const cb = callback === undefined ? null : callback;
241 if (__DEV__) {
242 - warnOnInvalidCallback(callback, 'render');
242 + warnOnInvalidCallback(cb, 'render');
243 }
244 - updateContainer(null, root, null, callback);
244 + const container = root.containerInfo;
245 + updateContainer(null, root, null, () => {
246 + unmarkContainerAsRoot(container);
247 + if (cb !== null) {
248 + cb();
249 + }
250 + });
251 };
252
253 /**
@@ -448,12 +454,17 @@ const ReactDOM: Object = {
454 'Target container is not a DOM element.',
455 );
456 if (__DEV__) {
451 - warningWithoutStack(
452 - !container._reactHasBeenPassedToCreateRootDEV,
453 - 'You are calling ReactDOM.hydrate() on a container that was previously ' +
454 - 'passed to ReactDOM.createRoot(). This is not supported. ' +
455 - 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
456 - );
457 + const isModernRoot =
458 + isContainerMarkedAsRoot(container) &&
459 + container._reactRootContainer === undefined;
460 + if (isModernRoot) {
461 + warningWithoutStack(
462 + false,
463 + 'You are calling ReactDOM.hydrate() on a container that was previously ' +
464 + 'passed to ReactDOM.createRoot(). This is not supported. ' +
465 + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
466 + );
467 + }
468 }
469 // TODO: throw or warn if we couldn't hydrate?
470 return legacyRenderSubtreeIntoContainer(
@@ -475,12 +486,17 @@ const ReactDOM: Object = {
486 'Target container is not a DOM element.',
487 );
488 if (__DEV__) {
478 - warningWithoutStack(
479 - !container._reactHasBeenPassedToCreateRootDEV,
480 - 'You are calling ReactDOM.render() on a container that was previously ' +
481 - 'passed to ReactDOM.createRoot(). This is not supported. ' +
482 - 'Did you mean to call root.render(element)?',
483 - );
489 + const isModernRoot =
490 + isContainerMarkedAsRoot(container) &&
491 + container._reactRootContainer === undefined;
492 + if (isModernRoot) {
493 + warningWithoutStack(
494 + false,
495 + 'You are calling ReactDOM.render() on a container that was previously ' +
496 + 'passed to ReactDOM.createRoot(). This is not supported. ' +
497 + 'Did you mean to call root.render(element)?',
498 + );
499 + }
500 }
501 return legacyRenderSubtreeIntoContainer(
502 null,
@@ -521,11 +537,16 @@ const ReactDOM: Object = {
537 );
538
539 if (__DEV__) {
524 - warningWithoutStack(
525 - !container._reactHasBeenPassedToCreateRootDEV,
526 - 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
527 - 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
528 - );
540 + const isModernRoot =
541 + isContainerMarkedAsRoot(container) &&
542 + container._reactRootContainer === undefined;
543 + if (isModernRoot) {
544 + warningWithoutStack(
545 + false,
546 + 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
547 + 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
548 + );
549 + }
550 }
551
552 if (container._reactRootContainer) {
@@ -543,6 +564,7 @@ const ReactDOM: Object = {
564 unbatchedUpdates(() => {
565 legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
566 container._reactRootContainer = null;
567 + unmarkContainerAsRoot(container);
568 });
569 });
570 // If you call unmountComponentAtNode twice in quick succession, you'll
@@ -650,12 +672,22 @@ function createBlockingRoot(
672
673 function warnIfReactDOMContainerInDEV(container) {
674 if (__DEV__) {
653 - warningWithoutStack(
654 - !container._reactRootContainer,
655 - 'You are calling ReactDOM.createRoot() on a container that was previously ' +
656 - 'passed to ReactDOM.render(). This is not supported.',
657 - );
658 - container._reactHasBeenPassedToCreateRootDEV = true;
675 + if (isContainerMarkedAsRoot(container)) {
676 + if (container._reactRootContainer) {
677 + warningWithoutStack(
678 + false,
679 + 'You are calling ReactDOM.createRoot() on a container that was previously ' +
680 + 'passed to ReactDOM.render(). This is not supported.',
681 + );
682 + } else {
683 + warningWithoutStack(
684 + false,
685 + 'You are calling ReactDOM.createRoot() on a container that ' +
686 + 'has already been passed to createRoot() before. Instead, call ' +
687 + 'root.render() on the existing root instead if you want to update it.',
688 + );
689 + }
690 + }
691 }
692 }
693
packages/react-dom/src/client/ReactDOMComponentTree.js
+8
@@ -30,6 +30,14 @@ export function markContainerAsRoot(hostRoot, node) {
30 node[internalContainerInstanceKey] = hostRoot;
31 }
32
33 +export function unmarkContainerAsRoot(node) {
34 + node[internalContainerInstanceKey] = null;
35 +}
36 +
37 +export function isContainerMarkedAsRoot(node) {
38 + return !!node[internalContainerInstanceKey];
39 +}
40 +
41 // Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
42 // If the target node is part of a hydrated or not yet rendered subtree, then
43 // this may also return a SuspenseComponent or HostRoot to indicate that.