Skip to content

Commit

Permalink
Merge pull request #8919 from acdlite/fibermounttests
Browse files Browse the repository at this point in the history
[Fiber] Mount/unmount warnings and invariants
  • Loading branch information
acdlite authored Feb 7, 2017
2 parents 23f9e7b + 0564b08 commit 5cfff87
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 24 deletions.
2 changes: 0 additions & 2 deletions scripts/fiber/tests-failing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
* can reconcile text arbitrarily split into multiple nodes on some substitutions only

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

Expand Down
3 changes: 0 additions & 3 deletions scripts/fiber/tests-passing-except-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js

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

src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
* should warn when unmounting a non-container root node
Expand Down
5 changes: 5 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -769,10 +769,15 @@ src/renderers/dom/shared/__tests__/ReactEventListener-test.js

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

src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
Expand Down
79 changes: 76 additions & 3 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ function validateContainer(container) {
}
}

function getReactRootElementInContainer(container : any) {
if (!container) {
return null;
}

if (container.nodeType === DOC_NODE_TYPE) {
return container.documentElement;
} else {
return container.firstChild;
}
}

function shouldAutoFocusHostComponent(
type : string,
props : Props,
Expand Down Expand Up @@ -366,9 +378,59 @@ var ReactDOM = {
// Top-level check occurs here instead of inside child reconciler because
// because requirements vary between renderers. E.g. React Art
// allows arrays.
invariant(
isValidElement(element),
'render(): Invalid component element.'
if (!isValidElement(element)) {
if (typeof element === 'string') {
invariant(
false,
'ReactDOM.render(): Invalid component element. Instead of ' +
'passing a string like \'div\', pass ' +
'React.createElement(\'div\') or <div />.'
);
} else if (typeof element === 'function') {
invariant(
false,
'ReactDOM.render(): Invalid component element. Instead of ' +
'passing a class like Foo, pass React.createElement(Foo) ' +
'or <Foo />.'
);
} else if (element != null && typeof element.props !== 'undefined') {
// Check if it quacks like an element
invariant(
false,
'ReactDOM.render(): Invalid component element. This may be ' +
'caused by unintentionally loading two independent copies ' +
'of React.'
);
} else {
invariant(
false,
'ReactDOM.render(): Invalid component element.'
);
}
}
}

if (__DEV__) {
const isRootRenderedBySomeReact = Boolean(container._reactRootContainer);
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = Boolean(rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl));

warning(
!hasNonRootReactChild ||
isRootRenderedBySomeReact,
'render(...): Replacing React-rendered children with a new root ' +
'component. If you intended to update the children of this node, ' +
'you should instead have the existing children update their state ' +
'and render the new components instead of calling ReactDOM.render.'
);

warning(
!container.tagName || container.tagName.toUpperCase() !== 'BODY',
'render(): Rendering components directly into 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 rendering into a container element created ' +
'for your app.'
);
}

Expand All @@ -394,7 +456,18 @@ var ReactDOM = {
'unmountComponentAtNode(...): Target container is not a DOM element.'
);
warnAboutUnstableUse();

if (container._reactRootContainer) {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const renderedByDifferentReact = rootEl && !ReactDOMComponentTree.getInstanceFromNode(rootEl);
warning(
!renderedByDifferentReact,
'unmountComponentAtNode(): The node you\'re attempting to unmount ' +
'was rendered by another copy of React.'
);
}

// Unmount should not be batched.
return DOMRenderer.unbatchedUpdates(() => {
return renderSubtreeIntoContainer(null, null, container, () => {
Expand Down
45 changes: 29 additions & 16 deletions src/renderers/dom/stack/client/ReactMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,23 +434,36 @@ var ReactMount = {

_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
validateCallback(callback, 'ReactDOM.render');
invariant(
React.isValidElement(nextElement),
'ReactDOM.render(): Invalid component element.%s',
(
typeof nextElement === 'string' ?
' Instead of passing a string like \'div\', pass ' +
'React.createElement(\'div\') or <div />.' :
typeof nextElement === 'function' ?
' Instead of passing a class like Foo, pass ' +
'React.createElement(Foo) or <Foo />.' :
if (!React.isValidElement(nextElement)) {
if (typeof nextElement === 'string') {
invariant(
false,
'ReactDOM.render(): Invalid component element. Instead of ' +
'passing a string like \'div\', pass ' +
'React.createElement(\'div\') or <div />.'
);
} else if (typeof nextElement === 'function') {
invariant(
false,
'ReactDOM.render(): Invalid component element. Instead of ' +
'passing a class like Foo, pass React.createElement(Foo) ' +
'or <Foo />.'
);
} else if (nextElement != null && typeof nextElement.props !== 'undefined') {
// Check if it quacks like an element
nextElement != null && nextElement.props !== undefined ?
' This may be caused by unintentionally loading two independent ' +
'copies of React.' :
''
)
);
invariant(
false,
'ReactDOM.render(): Invalid component element. This may be ' +
'caused by unintentionally loading two independent copies ' +
'of React.'
);
} else {
invariant(
false,
'ReactDOM.render(): Invalid component element.'
);
}
}

warning(
!container || !container.tagName ||
Expand Down

0 comments on commit 5cfff87

Please sign in to comment.