Revert "Add key warning to nested collections"
This heuristic isn't great because it relies on inspecting deep children which aren't guaranteed to be React elements. In particular, this was causing stack overflows in a component we had that used a *DOM node* as children, like `<DOMContainer>{node}</DOMContainer>`. This reverts commits: 0a3aa8493aa014668f5dd3bac30868c18397538e 64c9d9d7629600da81ceefcaa59c46e64d030874 0c58f4f6b12bd7856b73f14163db905cb13c5fc7 8cf226e44241aeafe147f6256a1351b46ac3cf91 086636747f26b577b4a4577a0888118310ee91b3
Ben Alpert committed
May 14, 2015 at 16:33 UTC
39402fe75d3657615aa9b4ae0be73dab2665321e
4 files changed
+37
-111
src/addons/__tests__/ReactFragment-test.js
+3
-3
@@ -49,13 +49,13 @@ describe('ReactFragment', function() {
49
z: <span />
50
};
51
var element = <div>{[children]}</div>;
52
+ expect(console.error.calls.length).toBe(0);
53
+ var container = document.createElement('div');
54
+ React.render(element, container);
55
expect(console.error.calls.length).toBe(1);
56
expect(console.error.calls[0].args[0]).toContain(
57
'Any use of a keyed object'
58
);
56
- var container = document.createElement('div');
57
- React.render(element, container);
58
- expect(console.error.calls.length).toBe(1);
59
});
60
61
it('should warn if accessing any property on a fragment', function() {
src/isomorphic/classic/element/ReactElement.js
+19
-21
@@ -80,21 +80,6 @@ function defineMutationMembrane(prototype) {
80
}
81
}
82
83
-function markChildArrayValidated(childArray) {
84
- // To make comparing ReactElements easier for testing purposes, we make the
85
- // validation flag non-enumerable (where possible, which should include every
86
- // environment we run tests in), so the test framework ignores it.
87
- try {
88
- Object.defineProperty(childArray, '_reactChildKeysValidated', {
89
- configurable: false,
90
- enumerable: false,
91
- writable: true
92
- });
93
- } catch (x) {
94
- }
95
- childArray._reactChildKeysValidated = true;
96
-}
97
-
83
/**
84
* Base constructor for all React elements. This is only used to make this
85
* work with a dynamic instanceof check. Nothing should live on this prototype.
@@ -121,6 +106,20 @@ var ReactElement = function(type, key, ref, owner, context, props) {
106
// commonly used development environments.
107
this._store = {props: props, originalProps: assign({}, props)};
108
109
+ // To make comparing ReactElements easier for testing purposes, we make
110
+ // the validation flag non-enumerable (where possible, which should
111
+ // include every environment we run tests in), so the test framework
112
+ // ignores it.
113
+ try {
114
+ Object.defineProperty(this._store, 'validated', {
115
+ configurable: false,
116
+ enumerable: false,
117
+ writable: true
118
+ });
119
+ } catch (x) {
120
+ }
121
+ this._store.validated = false;
122
+
123
// We're not allowed to set props directly on the object so we early
124
// return and rely on the prototype membrane to forward to the backing
125
// store.
@@ -171,9 +170,6 @@ ReactElement.createElement = function(type, config, children) {
170
props.children = children;
171
} else if (childrenLength > 1) {
172
var childArray = Array(childrenLength);
174
- if (__DEV__) {
175
- markChildArrayValidated(childArray);
176
- }
173
for (var i = 0; i < childrenLength; i++) {
174
childArray[i] = arguments[i + 2];
175
}
@@ -220,6 +216,11 @@ ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
216
oldElement._context,
217
newProps
218
);
219
+
220
+ if (__DEV__) {
221
+ // If the key on the original is valid, then the clone is valid
222
+ newElement._store.validated = oldElement._store.validated;
223
+ }
224
return newElement;
225
};
226
@@ -261,9 +262,6 @@ ReactElement.cloneElement = function(element, config, children) {
262
props.children = children;
263
} else if (childrenLength > 1) {
264
var childArray = Array(childrenLength);
264
- if (__DEV__) {
265
- markChildArrayValidated(childArray);
266
- }
265
for (var i = 0; i < childrenLength; i++) {
266
childArray[i] = arguments[i + 2];
267
}
src/isomorphic/classic/element/ReactElementValidator.js
+15
-37
@@ -90,19 +90,15 @@ function getCurrentOwnerDisplayName() {
90
* @internal
91
* @param {ReactElement} element Element that requires a key.
92
* @param {*} parentType element's parent's type.
93
- * @param {boolean} deep false if top-level collection, true if nested
93
*/
95
-function validateExplicitKey(element, parentType, deep) {
96
- if (element.key != null) {
94
+function validateExplicitKey(element, parentType) {
95
+ if (element._store.validated || element.key != null) {
96
return;
97
}
98
+ element._store.validated = true;
99
+
100
warnAndMonitorForKeyUse(
100
- // We vary the message for nested key warning to allow filtering them out
101
- // since we didn't historically warn in this case.
102
- deep ?
103
- 'Each child in a nested array or iterator should have ' +
104
- 'a unique "key" prop.' :
105
- 'Each child in an array or iterator should have a unique "key" prop.',
101
+ 'Each child in an array or iterator should have a unique "key" prop.',
102
element,
103
parentType
104
);
@@ -116,17 +112,13 @@ function validateExplicitKey(element, parentType, deep) {
112
* @param {string} name Property name of the key.
113
* @param {ReactElement} element Component that requires a key.
114
* @param {*} parentType element's parent's type.
119
- * @param {boolean} deep false if top-level collection, true if nested
115
*/
121
-function validatePropertyKey(name, element, parentType, deep) {
116
+function validatePropertyKey(name, element, parentType) {
117
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
118
return;
119
}
120
warnAndMonitorForKeyUse(
126
- deep ?
127
- 'Nested child objects should have non-numeric keys ' +
128
- 'so ordering is preserved.' :
129
- 'Child objects should have non-numeric keys so ordering is preserved.',
121
+ 'Child objects should have non-numeric keys so ordering is preserved.',
122
element,
123
parentType
124
);
@@ -188,29 +180,18 @@ function warnAndMonitorForKeyUse(message, element, parentType) {
180
* @internal
181
* @param {ReactNode} node Statically passed child of any type.
182
* @param {*} parentType node's parent's type.
191
- * @param {boolean} deep false if top-level collection, true if nested
183
*/
193
-function validateChildKeys(node, parentType, deep) {
184
+function validateChildKeys(node, parentType) {
185
if (Array.isArray(node)) {
195
- if (node._reactChildKeysValidated) {
196
- // All child elements were passed in a valid location.
197
- return;
198
- }
186
for (var i = 0; i < node.length; i++) {
187
var child = node[i];
188
if (ReactElement.isValidElement(child)) {
202
- validateExplicitKey(child, parentType, deep);
203
- } else {
204
- // TODO: Warn on unkeyed arrays and suggest using createFragment
205
- validateChildKeys(child, parentType, true);
189
+ validateExplicitKey(child, parentType);
190
}
191
}
208
- } else if (
209
- typeof node === 'string' || typeof node === 'number' ||
210
- ReactElement.isValidElement(node)
211
- ) {
192
+ } else if (ReactElement.isValidElement(node)) {
193
// This element was passed in a valid location.
213
- return;
194
+ node._store.validated = true;
195
} else if (node) {
196
var iteratorFn = getIteratorFn(node);
197
// Entry iterators provide implicit keys.
@@ -220,9 +201,7 @@ function validateChildKeys(node, parentType, deep) {
201
var step;
202
while (!(step = iterator.next()).done) {
203
if (ReactElement.isValidElement(step.value)) {
223
- validateExplicitKey(step.value, parentType, deep);
224
- } else {
225
- validateChildKeys(step.value, parentType, true);
204
+ validateExplicitKey(step.value, parentType);
205
}
206
}
207
}
@@ -230,8 +209,7 @@ function validateChildKeys(node, parentType, deep) {
209
var fragment = ReactFragment.extractIfFragment(node);
210
for (var key in fragment) {
211
if (fragment.hasOwnProperty(key)) {
233
- validatePropertyKey(key, fragment[key], parentType, deep);
234
- validateChildKeys(fragment[key], parentType, true);
212
+ validatePropertyKey(key, fragment[key], parentType);
213
}
214
}
215
}
@@ -437,7 +415,7 @@ var ReactElementValidator = {
415
}
416
417
for (var i = 2; i < arguments.length; i++) {
440
- validateChildKeys(arguments[i], type, false);
418
+ validateChildKeys(arguments[i], type);
419
}
420
421
validatePropTypes(element);
@@ -485,7 +463,7 @@ var ReactElementValidator = {
463
cloneElement: function(element, props, children) {
464
var newElement = ReactElement.cloneElement.apply(this, arguments);
465
for (var i = 2; i < arguments.length; i++) {
488
- validateChildKeys(arguments[i], newElement.type, false);
466
+ validateChildKeys(arguments[i], newElement.type);
467
}
468
validatePropTypes(newElement);
469
return newElement;
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
-50
@@ -120,56 +120,6 @@ describe('ReactElementValidator', function() {
120
);
121
});
122
123
- it('warns for keys for nested arrays of elements', function() {
124
- spyOn(console, 'error');
125
-
126
- var divs = [
127
- [
128
- <div />,
129
- <div />
130
- ],
131
- <div key="foo" />
132
- ];
133
- ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
134
-
135
- expect(console.error.argsForCall.length).toBe(1);
136
- expect(console.error.argsForCall[0][0]).toBe(
137
- 'Warning: Each child in a nested array or iterator should have a ' +
138
- 'unique "key" prop. Check the React.render call using <div>. See ' +
139
- 'https://fb.me/react-warning-keys for more information.'
140
- );
141
- });
142
-
143
- it('warns for keys when reusing children', function() {
144
- spyOn(console, 'error');
145
-
146
- var f = <span />;
147
- var g = <span />;
148
-
149
- var children = [f, g];
150
-
151
- ReactTestUtils.renderIntoDocument(
152
- <div>
153
- <div key="0">
154
- {g}
155
- </div>
156
- <div key="1">
157
- {f}
158
- </div>
159
- <div key="2">
160
- {children}
161
- </div>
162
- </div>
163
- );
164
-
165
- expect(console.error.argsForCall.length).toBe(1);
166
- expect(console.error.argsForCall[0][0]).toBe(
167
- 'Warning: Each child in an array or iterator should have a unique ' +
168
- '"key" prop. Check the React.render call using <div>. See ' +
169
- 'https://fb.me/react-warning-keys for more information.'
170
- );
171
- });
172
-
123
it('does not warn for keys when passing children down', function() {
124
spyOn(console, 'error');
125