diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 42d4cb62c53a5..898df20c5659f 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -155,9 +155,21 @@ function validatePropertyKey(name) { */ function validateChildKeys(component) { if (Array.isArray(component)) { + var keys = {}; for (var i = 0; i < component.length; i++) { var child = component[i]; if (ReactComponent.isValidComponent(child)) { + if (child.props.key != null) { + if (keys[child.props.key]) { + var currentName = ReactCurrentOwner.current.constructor.displayName; + console.warn( + 'Key collision detected for key "' + child.props.key + '". ' + + 'Check the render method of ' + currentName + '.' + ); + } + + keys[child.props.key] = true; + } validateExplicitKey(child); } } diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 1899cfb7ccbaf..1035a09d9e63b 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -22,13 +22,23 @@ var React; var ReactTestUtils; +var mocks; var reactComponentExpect; +var warn; describe('ReactComponent', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); reactComponentExpect = require('reactComponentExpect'); + mocks = require('mocks'); + + warn = console.warn; + console.warn = mocks.getMockFunction(); + }); + + afterEach(function() { + console.warn = warn; }); it('should throw on invalid render targets', function() { @@ -207,4 +217,30 @@ describe('ReactComponent', function() { expect(root.refs.switcher.refs.box.refs.boxDiv._mountDepth).toBe(3); expect(root.refs.child.refs.span._mountDepth).toBe(6); }); + + it('should render even with key collisions', function() { + var Component = React.createClass({ + render: function() { + var array = [ + , + , + , + , + + ]; + + return
{array}
; + } + }); + + var instance = ; + ReactTestUtils.renderIntoDocument(instance); + + expect(console.warn.mock.calls.length).toBe(1); + + expect(console.warn.mock.calls[0][0]).toBe( + 'Key collision detected for key "1". ' + + 'Check the render method of Component.' + ); + }); }); diff --git a/src/utils/__tests__/ReactChildren-test.js b/src/utils/__tests__/ReactChildren-test.js index b256d3fdf5fa0..544f78134c0e4 100644 --- a/src/utils/__tests__/ReactChildren-test.js +++ b/src/utils/__tests__/ReactChildren-test.js @@ -319,18 +319,4 @@ describe('ReactChildren', function() { ReactChildren.map(instance.props.children, mapFn); }).not.toThrow(); }); - - it('should throw if key provided is a dupe with explicit key', function() { - var zero =
; - var one =
; - - var mapFn = function() {return null;}; - var instance = ( -
{zero}{one}
- ); - - expect(function() { - ReactChildren.map(instance.props.children, mapFn); - }).toThrow(); - }); }); diff --git a/src/utils/flattenChildren.js b/src/utils/flattenChildren.js index 77650b67aa93a..f43f21a55d0fe 100644 --- a/src/utils/flattenChildren.js +++ b/src/utils/flattenChildren.js @@ -18,7 +18,6 @@ "use strict"; -var invariant = require('invariant'); var traverseAllChildren = require('traverseAllChildren'); /** @@ -27,16 +26,8 @@ var traverseAllChildren = require('traverseAllChildren'); * @param {!string} name String name of key path to child. */ function flattenSingleChildIntoContext(traverseContext, child, name) { - // We found a component instance. - var result = traverseContext; - invariant( - !result.hasOwnProperty(name), - 'flattenChildren(...): Encountered two children with the same key, `%s`. ' + - 'Children keys must be unique.', - name - ); if (child != null) { - result[name] = child; + traverseContext[name] = child; } } diff --git a/src/utils/traverseAllChildren.js b/src/utils/traverseAllChildren.js index 2fa6861f34bf1..e6969e428b5e5 100644 --- a/src/utils/traverseAllChildren.js +++ b/src/utils/traverseAllChildren.js @@ -98,13 +98,18 @@ function wrapUserProvidedKey(key) { var traverseAllChildrenImpl = function(children, nameSoFar, indexSoFar, callback, traverseContext) { var subtreeCount = 0; // Count of children found in the current subtree. + var key; if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { var child = children[i]; + key = getComponentKey(child, i); + if (traverseContext && traverseContext.hasOwnProperty(key)) { + key = getComponentKey(null, i); + } var nextName = ( nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) + - getComponentKey(child, i) + key ); var nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( @@ -136,7 +141,7 @@ var traverseAllChildrenImpl = 'traverseAllChildren(...): Encountered an invalid child; DOM ' + 'elements are not valid children of React components.' ); - for (var key in children) { + for (key in children) { if (children.hasOwnProperty(key)) { subtreeCount += traverseAllChildrenImpl( children[key],