Skip to content

Commit

Permalink
Warn if people mutate children.
Browse files Browse the repository at this point in the history
  • Loading branch information
jimfb authored and jim committed Jun 8, 2016
1 parent 99d8524 commit 31ebcb7
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/isomorphic/classic/element/ReactElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ var ReactElement = function(type, key, ref, self, source, owner, props) {
writable: false,
value: self,
});
Object.defineProperty(element, '_shadowChildren', {
configurable: false,
enumerable: false,
writable: false,
value: Array.isArray(props.children) ? props.children.slice(0) : props.children,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
Expand Down
2 changes: 2 additions & 0 deletions src/renderers/shared/ReactDebugTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,11 @@ if (__DEV__) {
var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
var ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
var ReactChildrenMutationWarningDevtool = require('ReactChildrenMutationWarningDevtool');
ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
ReactDebugTool.addDevtool(ReactChildrenMutationWarningDevtool);
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if ((/[?&]react_perf\b/).test(url)) {
ReactDebugTool.beginProfiling();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactChildrenMutationWarningDevtool
*/

'use strict';

var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');

var warning = require('warning');

function handleElement(debugID, element) {
if (element == null) {
return;
}
if (element._shadowChildren === undefined) {
return;
}
if (element._shadowChildren === element.props.children) {
return;
}
var isMutated = false;
if (Array.isArray(element._shadowChildren)) {
if (element._shadowChildren.length === element.props.children.length) {
for (var i = 0; i < element._shadowChildren.length; i++) {
if (element._shadowChildren[i] !== element.props.children[i]) {
isMutated = true;
}
}
} else {
isMutated = true;
}
}
warning(
Array.isArray(element._shadowChildren) && !isMutated,
'Component\'s children should not be mutated.%s',
ReactComponentTreeDevtool.getStackAddendumByID(debugID),
);
}

var ReactDOMUnknownPropertyDevtool = {
onBeforeMountComponent(debugID, element) {
handleElement(debugID, element);
},
onBeforeUpdateComponent(debugID, element) {
handleElement(debugID, element);
},
};

module.exports = ReactDOMUnknownPropertyDevtool;
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ var React;
var ReactDOM;
var ReactTestUtils;

function normalizeCodeLocInfo(str) {
return str.replace(/\(at .+?:\d+\)/g, '(at **)');
}

describe('ReactComponent', function() {
beforeEach(function() {
React = require('React');
Expand Down Expand Up @@ -45,6 +49,18 @@ describe('ReactComponent', function() {
}).toThrow();
});

it('should warn when children are mutated', function() {
spyOn(console, 'error');
var children = [<span key={0} />, <span key={1} />, <span key={2} />];
var element = <div>{children}</div>;
children[1] = <p key={1} />; // Mutation is illegal
ReactTestUtils.renderIntoDocument(element);
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Component\'s children should not be mutated.\n in div (at **)'
);
});

it('should support refs on owned components', function() {
var innerObj = {};
var outerObj = {};
Expand Down

0 comments on commit 31ebcb7

Please sign in to comment.