Add key warning to nested collections

Also when reusing elements in multiple contexts -- before we were mutating each element to indicate its validity; now we mutate the array containing it (which we create, in the case of rest-arg children).

Fixes #2496. Fixes #3348.
This commit is contained in:
Ben Alpert
2015-04-27 14:41:25 -07:00
parent 8e9deff3cc
commit 086636747f
4 changed files with 105 additions and 26 deletions
+3 -3
View File
@@ -49,13 +49,13 @@ describe('ReactFragment', function() {
z: <span />
};
var element = <div>{[children]}</div>;
expect(console.error.calls.length).toBe(0);
var container = document.createElement('div');
React.render(element, container);
expect(console.error.calls.length).toBe(1);
expect(console.error.calls[0].args[0]).toContain(
'Any use of a keyed object'
);
var container = document.createElement('div');
React.render(element, container);
expect(console.error.calls.length).toBe(1);
});
it('should warn if accessing any property on a fragment', function() {
+15 -19
View File
@@ -106,20 +106,6 @@ var ReactElement = function(type, key, ref, owner, context, props) {
// commonly used development environments.
this._store = {props: props, originalProps: assign({}, props)};
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
try {
Object.defineProperty(this._store, 'validated', {
configurable: false,
enumerable: false,
writable: true
});
} catch (x) {
}
this._store.validated = false;
// We're not allowed to set props directly on the object so we early
// return and rely on the prototype membrane to forward to the backing
// store.
@@ -170,6 +156,21 @@ ReactElement.createElement = function(type, config, children) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
try {
Object.defineProperty(childArray, '_reactChildKeysValidated', {
configurable: false,
enumerable: false,
writable: true
});
} catch (x) {
}
childArray._reactChildKeysValidated = true;
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
@@ -216,11 +217,6 @@ ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
oldElement._context,
newProps
);
if (__DEV__) {
// If the key on the original is valid, then the clone is valid
newElement._store.validated = oldElement._store.validated;
}
return newElement;
};
+12 -4
View File
@@ -92,11 +92,9 @@ function getCurrentOwnerDisplayName() {
* @param {*} parentType element's parent's type.
*/
function validateExplicitKey(element, parentType) {
if (element._store.validated || element.key != null) {
if (element.key != null) {
return;
}
element._store.validated = true;
warnAndMonitorForKeyUse(
'Each child in an array or iterator should have a unique "key" prop.',
element,
@@ -183,15 +181,22 @@ function warnAndMonitorForKeyUse(message, element, parentType) {
*/
function validateChildKeys(node, parentType) {
if (Array.isArray(node)) {
if (node._reactChildKeysValidated) {
// All child elements were passed in a valid location.
return;
}
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (ReactElement.isValidElement(child)) {
validateExplicitKey(child, parentType);
} else {
// TODO: Warn on unkeyed arrays and suggest using createFragment
validateChildKeys(child, parentType);
}
}
} else if (ReactElement.isValidElement(node)) {
// This element was passed in a valid location.
node._store.validated = true;
return;
} else if (node) {
var iteratorFn = getIteratorFn(node);
// Entry iterators provide implicit keys.
@@ -202,6 +207,8 @@ function validateChildKeys(node, parentType) {
while (!(step = iterator.next()).done) {
if (ReactElement.isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
} else {
validateChildKeys(step.value, parentType);
}
}
}
@@ -210,6 +217,7 @@ function validateChildKeys(node, parentType) {
for (var key in fragment) {
if (fragment.hasOwnProperty(key)) {
validatePropertyKey(key, fragment[key], parentType);
validateChildKeys(fragment[key], parentType);
}
}
}
@@ -120,6 +120,81 @@ describe('ReactElementValidator', function() {
);
});
it('warns for keys for nested arrays of elements', function() {
spyOn(console, 'error');
var divs = [
[
<div />,
<div />
],
<div key="foo" />
];
ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Each child in an array or iterator should have a unique ' +
'"key" prop. Check the React.render call using <div>. See ' +
'https://fb.me/react-warning-keys for more information.'
);
});
it('warns for keys when reusing children', function() {
spyOn(console, 'error');
var f = <span />;
var g = <span />;
var children = [f, g];
return (
<div>
<div key="0">
{g}
</div>
<div key="1">
{f}
</div>
<div key="2">
{children}
</div>
</div>
);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Each child in an array or iterator should have a unique ' +
'"key" prop. Check the React.render call using <div>. See ' +
'https://fb.me/react-warning-keys for more information.'
);
});
it('does not warn for keys when passing children down', function() {
spyOn(console, 'error');
debugger;
var Wrapper = React.createClass({
render: function() {
return (
<div>
{this.props.children}
<footer />
</div>
);
}
});
ReactTestUtils.renderIntoDocument(
<Wrapper>
<span />
<span />
</Wrapper>
);
expect(console.error.argsForCall.length).toBe(0);
});
it('warns for keys for iterables of elements in rest args', function() {
spyOn(console, 'error');
var Component = React.createFactory(ComponentClass);