Skip to content

Commit 5cfff87

Browse files
authored
Merge pull request #8919 from acdlite/fibermounttests
[Fiber] Mount/unmount warnings and invariants
2 parents 23f9e7b + 0564b08 commit 5cfff87

File tree

5 files changed

+110
-24
lines changed

5 files changed

+110
-24
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
1515
* can reconcile text arbitrarily split into multiple nodes on some substitutions only
1616

1717
src/renderers/dom/shared/__tests__/ReactMount-test.js
18-
* throws when given a string
19-
* throws when given a factory
2018
* tracks root instances
2119
* marks top-level mounts
2220

scripts/fiber/tests-passing-except-dev.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
77

88
src/renderers/dom/shared/__tests__/ReactMount-test.js
99
* should warn if mounting into dirty rendered markup
10-
* should warn when mounting into document.body
1110
* should account for escaping on a checksum mismatch
12-
* should warn if render removes React-rendered children
13-
* should warn if the unmounted node was rendered by another copy of React
1411

1512
src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
1613
* should warn when unmounting a non-container root node

scripts/fiber/tests-passing.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,10 +769,15 @@ src/renderers/dom/shared/__tests__/ReactEventListener-test.js
769769

770770
src/renderers/dom/shared/__tests__/ReactMount-test.js
771771
* throws when given a non-node
772+
* throws when given a string
773+
* throws when given a factory
772774
* should render different components in same root
773775
* should unmount and remount if the key changes
774776
* should reuse markup if rendering to the same target twice
775777
* should not warn if mounting into non-empty node
778+
* should warn when mounting into document.body
779+
* should warn if render removes React-rendered children
780+
* should warn if the unmounted node was rendered by another copy of React
776781
* passes the correct callback context
777782

778783
src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ function validateContainer(container) {
108108
}
109109
}
110110

111+
function getReactRootElementInContainer(container : any) {
112+
if (!container) {
113+
return null;
114+
}
115+
116+
if (container.nodeType === DOC_NODE_TYPE) {
117+
return container.documentElement;
118+
} else {
119+
return container.firstChild;
120+
}
121+
}
122+
111123
function shouldAutoFocusHostComponent(
112124
type : string,
113125
props : Props,
@@ -366,9 +378,59 @@ var ReactDOM = {
366378
// Top-level check occurs here instead of inside child reconciler because
367379
// because requirements vary between renderers. E.g. React Art
368380
// allows arrays.
369-
invariant(
370-
isValidElement(element),
371-
'render(): Invalid component element.'
381+
if (!isValidElement(element)) {
382+
if (typeof element === 'string') {
383+
invariant(
384+
false,
385+
'ReactDOM.render(): Invalid component element. Instead of ' +
386+
'passing a string like \'div\', pass ' +
387+
'React.createElement(\'div\') or <div />.'
388+
);
389+
} else if (typeof element === 'function') {
390+
invariant(
391+
false,
392+
'ReactDOM.render(): Invalid component element. Instead of ' +
393+
'passing a class like Foo, pass React.createElement(Foo) ' +
394+
'or <Foo />.'
395+
);
396+
} else if (element != null && typeof element.props !== 'undefined') {
397+
// Check if it quacks like an element
398+
invariant(
399+
false,
400+
'ReactDOM.render(): Invalid component element. This may be ' +
401+
'caused by unintentionally loading two independent copies ' +
402+
'of React.'
403+
);
404+
} else {
405+
invariant(
406+
false,
407+
'ReactDOM.render(): Invalid component element.'
408+
);
409+
}
410+
}
411+
}
412+
413+
if (__DEV__) {
414+
const isRootRenderedBySomeReact = Boolean(container._reactRootContainer);
415+
const rootEl = getReactRootElementInContainer(container);
416+
const hasNonRootReactChild = Boolean(rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl));
417+
418+
warning(
419+
!hasNonRootReactChild ||
420+
isRootRenderedBySomeReact,
421+
'render(...): Replacing React-rendered children with a new root ' +
422+
'component. If you intended to update the children of this node, ' +
423+
'you should instead have the existing children update their state ' +
424+
'and render the new components instead of calling ReactDOM.render.'
425+
);
426+
427+
warning(
428+
!container.tagName || container.tagName.toUpperCase() !== 'BODY',
429+
'render(): Rendering components directly into document.body is ' +
430+
'discouraged, since its children are often manipulated by third-party ' +
431+
'scripts and browser extensions. This may lead to subtle ' +
432+
'reconciliation issues. Try rendering into a container element created ' +
433+
'for your app.'
372434
);
373435
}
374436

@@ -394,7 +456,18 @@ var ReactDOM = {
394456
'unmountComponentAtNode(...): Target container is not a DOM element.'
395457
);
396458
warnAboutUnstableUse();
459+
397460
if (container._reactRootContainer) {
461+
if (__DEV__) {
462+
const rootEl = getReactRootElementInContainer(container);
463+
const renderedByDifferentReact = rootEl && !ReactDOMComponentTree.getInstanceFromNode(rootEl);
464+
warning(
465+
!renderedByDifferentReact,
466+
'unmountComponentAtNode(): The node you\'re attempting to unmount ' +
467+
'was rendered by another copy of React.'
468+
);
469+
}
470+
398471
// Unmount should not be batched.
399472
return DOMRenderer.unbatchedUpdates(() => {
400473
return renderSubtreeIntoContainer(null, null, container, () => {

src/renderers/dom/stack/client/ReactMount.js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -434,23 +434,36 @@ var ReactMount = {
434434

435435
_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
436436
validateCallback(callback, 'ReactDOM.render');
437-
invariant(
438-
React.isValidElement(nextElement),
439-
'ReactDOM.render(): Invalid component element.%s',
440-
(
441-
typeof nextElement === 'string' ?
442-
' Instead of passing a string like \'div\', pass ' +
443-
'React.createElement(\'div\') or <div />.' :
444-
typeof nextElement === 'function' ?
445-
' Instead of passing a class like Foo, pass ' +
446-
'React.createElement(Foo) or <Foo />.' :
437+
if (!React.isValidElement(nextElement)) {
438+
if (typeof nextElement === 'string') {
439+
invariant(
440+
false,
441+
'ReactDOM.render(): Invalid component element. Instead of ' +
442+
'passing a string like \'div\', pass ' +
443+
'React.createElement(\'div\') or <div />.'
444+
);
445+
} else if (typeof nextElement === 'function') {
446+
invariant(
447+
false,
448+
'ReactDOM.render(): Invalid component element. Instead of ' +
449+
'passing a class like Foo, pass React.createElement(Foo) ' +
450+
'or <Foo />.'
451+
);
452+
} else if (nextElement != null && typeof nextElement.props !== 'undefined') {
447453
// Check if it quacks like an element
448-
nextElement != null && nextElement.props !== undefined ?
449-
' This may be caused by unintentionally loading two independent ' +
450-
'copies of React.' :
451-
''
452-
)
453-
);
454+
invariant(
455+
false,
456+
'ReactDOM.render(): Invalid component element. This may be ' +
457+
'caused by unintentionally loading two independent copies ' +
458+
'of React.'
459+
);
460+
} else {
461+
invariant(
462+
false,
463+
'ReactDOM.render(): Invalid component element.'
464+
);
465+
}
466+
}
454467

455468
warning(
456469
!container || !container.tagName ||

0 commit comments

Comments
 (0)