Skip to content

Commit 31ebcb7

Browse files
jimfbjim
authored andcommitted
Warn if people mutate children.
1 parent 99d8524 commit 31ebcb7

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-0
lines changed

src/isomorphic/classic/element/ReactElement.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ var ReactElement = function(type, key, ref, self, source, owner, props) {
116116
writable: false,
117117
value: self,
118118
});
119+
Object.defineProperty(element, '_shadowChildren', {
120+
configurable: false,
121+
enumerable: false,
122+
writable: false,
123+
value: Array.isArray(props.children) ? props.children.slice(0) : props.children,
124+
});
119125
// Two elements created in two different places should be considered
120126
// equal for testing purposes and therefore we hide it from enumeration.
121127
Object.defineProperty(element, '_source', {

src/renderers/shared/ReactDebugTool.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,11 @@ if (__DEV__) {
314314
var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
315315
var ReactHostOperationHistoryDevtool = require('ReactHostOperationHistoryDevtool');
316316
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
317+
var ReactChildrenMutationWarningDevtool = require('ReactChildrenMutationWarningDevtool');
317318
ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
318319
ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
319320
ReactDebugTool.addDevtool(ReactHostOperationHistoryDevtool);
321+
ReactDebugTool.addDevtool(ReactChildrenMutationWarningDevtool);
320322
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
321323
if ((/[?&]react_perf\b/).test(url)) {
322324
ReactDebugTool.beginProfiling();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactChildrenMutationWarningDevtool
10+
*/
11+
12+
'use strict';
13+
14+
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
15+
16+
var warning = require('warning');
17+
18+
function handleElement(debugID, element) {
19+
if (element == null) {
20+
return;
21+
}
22+
if (element._shadowChildren === undefined) {
23+
return;
24+
}
25+
if (element._shadowChildren === element.props.children) {
26+
return;
27+
}
28+
var isMutated = false;
29+
if (Array.isArray(element._shadowChildren)) {
30+
if (element._shadowChildren.length === element.props.children.length) {
31+
for (var i = 0; i < element._shadowChildren.length; i++) {
32+
if (element._shadowChildren[i] !== element.props.children[i]) {
33+
isMutated = true;
34+
}
35+
}
36+
} else {
37+
isMutated = true;
38+
}
39+
}
40+
warning(
41+
Array.isArray(element._shadowChildren) && !isMutated,
42+
'Component\'s children should not be mutated.%s',
43+
ReactComponentTreeDevtool.getStackAddendumByID(debugID),
44+
);
45+
}
46+
47+
var ReactDOMUnknownPropertyDevtool = {
48+
onBeforeMountComponent(debugID, element) {
49+
handleElement(debugID, element);
50+
},
51+
onBeforeUpdateComponent(debugID, element) {
52+
handleElement(debugID, element);
53+
},
54+
};
55+
56+
module.exports = ReactDOMUnknownPropertyDevtool;

src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ var React;
1515
var ReactDOM;
1616
var ReactTestUtils;
1717

18+
function normalizeCodeLocInfo(str) {
19+
return str.replace(/\(at .+?:\d+\)/g, '(at **)');
20+
}
21+
1822
describe('ReactComponent', function() {
1923
beforeEach(function() {
2024
React = require('React');
@@ -45,6 +49,18 @@ describe('ReactComponent', function() {
4549
}).toThrow();
4650
});
4751

52+
it('should warn when children are mutated', function() {
53+
spyOn(console, 'error');
54+
var children = [<span key={0} />, <span key={1} />, <span key={2} />];
55+
var element = <div>{children}</div>;
56+
children[1] = <p key={1} />; // Mutation is illegal
57+
ReactTestUtils.renderIntoDocument(element);
58+
expect(console.error.calls.count()).toBe(1);
59+
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
60+
'Warning: Component\'s children should not be mutated.\n in div (at **)'
61+
);
62+
});
63+
4864
it('should support refs on owned components', function() {
4965
var innerObj = {};
5066
var outerObj = {};

0 commit comments

Comments
 (0)