mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Warn when passing invalid containers to render and unmountComponentAtNode
This commit is contained in:
committed by
Ben Alpert
parent
4139b2e223
commit
270a805369
@@ -587,8 +587,16 @@ var ReactMount = {
|
||||
var reactRootElement = getReactRootElementInContainer(container);
|
||||
var containerHasReactMarkup =
|
||||
reactRootElement && ReactMount.isRenderedByReact(reactRootElement);
|
||||
var containerHasNonRootReactChild =
|
||||
ReactMount.hasNonRootReactChild(container);
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
!containerHasNonRootReactChild,
|
||||
'renderComponent(...): Replacing React-rendered children with a new ' +
|
||||
'root component.'
|
||||
);
|
||||
|
||||
if (!containerHasReactMarkup || reactRootElement.nextSibling) {
|
||||
var rootElementSibling = reactRootElement;
|
||||
while (rootElementSibling) {
|
||||
@@ -601,13 +609,15 @@ var ReactMount = {
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
rootElementSibling = rootElementSibling.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;
|
||||
var shouldReuseMarkup =
|
||||
containerHasReactMarkup &&
|
||||
!prevComponent &&
|
||||
!containerHasNonRootReactChild;
|
||||
var component = ReactMount._renderNewRootComponent(
|
||||
nextWrappedElement,
|
||||
container,
|
||||
@@ -697,6 +707,28 @@ var ReactMount = {
|
||||
var reactRootID = getReactRootID(container);
|
||||
var component = instancesByReactRootID[reactRootID];
|
||||
if (!component) {
|
||||
// Check if the node being unmounted was rendered by React, but isn't a
|
||||
// root node.
|
||||
var containerHasNonRootReactChild = ReactMount.hasNonRootReactChild(
|
||||
container);
|
||||
|
||||
// Check if the container itself is a React root node.
|
||||
var containerID = ReactMount.getID(container);
|
||||
var containerRootID = ReactInstanceHandles.getReactRootIDFromNodeID(
|
||||
containerID);
|
||||
var isContainerReactRoot =
|
||||
containerID && containerRootID && containerID === containerRootID;
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
!containerHasNonRootReactChild,
|
||||
'unmountComponentAtNode(): The node you\'re attempting to unmount ' +
|
||||
'is not a valid React root node, and thus cannot be unmounted.%s',
|
||||
(isContainerReactRoot ? ' You may have passed in a React root ' +
|
||||
'node as argument, rather than its container.' : '')
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
ReactUpdates.batchedUpdates(
|
||||
@@ -781,6 +813,22 @@ var ReactMount = {
|
||||
return id ? id.charAt(0) === SEPARATOR : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the supplied DOM node has a direct React-rendered child that is
|
||||
* not a React root element. Useful for warning in renderComponent`,
|
||||
* `unmountComponentAtNode`, etc.
|
||||
*
|
||||
* @param {?DOMElement} node The candidate DOM node.
|
||||
* @return {boolean} True if the DOM element contains a direct child that was
|
||||
* rendered by React but is not a root element.
|
||||
* @internal
|
||||
*/
|
||||
hasNonRootReactChild: function(node) {
|
||||
var reactRootID = getReactRootID(node);
|
||||
return reactRootID ? reactRootID !==
|
||||
ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Traverses up the ancestors of the supplied node to find a node that is a
|
||||
* DOM representation of a React component.
|
||||
|
||||
@@ -224,4 +224,24 @@ describe('ReactMount', function() {
|
||||
expect(console.error.mock.calls.length).toBe(1);
|
||||
expect(console.error.mock.calls[0][0]).toContain('two copies of React');
|
||||
});
|
||||
|
||||
it('should warn if render removes React-rendered children', function() {
|
||||
var container = document.createElement('container');
|
||||
var Component = React.createClass({
|
||||
render: function() {
|
||||
return <div><div /></div>;
|
||||
},
|
||||
});
|
||||
React.render(<Component />, container);
|
||||
|
||||
// Test that blasting away children throws a warning
|
||||
spyOn(console, 'error');
|
||||
var rootNode = container.firstChild;
|
||||
React.render(<span />, rootNode);
|
||||
expect(console.error.callCount).toBe(1);
|
||||
expect(console.error.mostRecentCall.args[0]).toBe(
|
||||
'Warning: renderComponent(...): Replacing React-rendered children ' +
|
||||
'with a new root component.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,18 +19,12 @@ describe('ReactMount', function() {
|
||||
var mainContainerDiv = document.createElement('div');
|
||||
document.body.appendChild(mainContainerDiv);
|
||||
|
||||
var instanceOne = (
|
||||
<div className="firstReactDiv">
|
||||
</div>
|
||||
);
|
||||
var instanceOne = <div className="firstReactDiv" />;
|
||||
var firstRootDiv = document.createElement('div');
|
||||
mainContainerDiv.appendChild(firstRootDiv);
|
||||
ReactDOM.render(instanceOne, firstRootDiv);
|
||||
|
||||
var instanceTwo = (
|
||||
<div className="secondReactDiv">
|
||||
</div>
|
||||
);
|
||||
var instanceTwo = <div className="secondReactDiv" />;
|
||||
var secondRootDiv = document.createElement('div');
|
||||
mainContainerDiv.appendChild(secondRootDiv);
|
||||
ReactDOM.render(instanceTwo, secondRootDiv);
|
||||
@@ -45,4 +39,49 @@ describe('ReactMount', function() {
|
||||
ReactDOM.unmountComponentAtNode(secondRootDiv);
|
||||
expect(secondRootDiv.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should warn when unmounting a non-container root node', function() {
|
||||
var mainContainerDiv = document.createElement('div');
|
||||
|
||||
var component =
|
||||
<div>
|
||||
<div />
|
||||
</div>;
|
||||
ReactDOM.render(component, mainContainerDiv);
|
||||
|
||||
// Test that unmounting at a root node gives a helpful warning
|
||||
var rootDiv = mainContainerDiv.firstChild;
|
||||
spyOn(console, 'error');
|
||||
ReactDOM.unmountComponentAtNode(rootDiv);
|
||||
expect(console.error.callCount).toBe(1);
|
||||
expect(console.error.mostRecentCall.args[0]).toBe(
|
||||
'Warning: unmountComponentAtNode(): The node you\'re attempting to ' +
|
||||
'unmount is not a valid React root node, and thus cannot be ' +
|
||||
'unmounted. You may have passed in a React root node as argument, ' +
|
||||
'rather than its container.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when unmounting a non-container, non-root node', function() {
|
||||
var mainContainerDiv = document.createElement('div');
|
||||
|
||||
var component =
|
||||
<div>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
</div>;
|
||||
ReactDOM.render(component, mainContainerDiv);
|
||||
|
||||
// Test that unmounting at a non-root node gives a different warning
|
||||
var nonRootDiv = mainContainerDiv.firstChild.firstChild;
|
||||
spyOn(console, 'error');
|
||||
ReactDOM.unmountComponentAtNode(nonRootDiv);
|
||||
expect(console.error.callCount).toBe(1);
|
||||
expect(console.error.mostRecentCall.args[0]).toBe(
|
||||
'Warning: unmountComponentAtNode(): The node you\'re attempting to ' +
|
||||
'unmount is not a valid React root node, and thus cannot be ' +
|
||||
'unmounted.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user