From b581c8cfc784b74e74af80307a799cb4437f7845 Mon Sep 17 00:00:00 2001 From: CommitSyncScript Date: Tue, 4 Jun 2013 12:15:40 -0700 Subject: [PATCH] Always reassign _key for every pass Currently we're mutating _key. Mutation here is fine, but it needs to be idempotent - which it's not. This is causing some issues. Instead I reassign the _key every time it passes through a flattening. This means that it's unique and stable for a single pass through a composite component. When it's repassed another level, it loses it previous identity and is rekeyed by it's new location. For auto-generated keys by index, this actually means it has the same semantics as before flattening. For explicit keys, it has the effect that keys need to be unique at every level. Regardless of how the key got there. Every component needs to ensure that it doesn't combine keys from two different sources that may collide. This is also inline with the old semantics but less intuitive in the new model. --- src/core/ReactComponent.js | 4 +- src/core/__tests__/ReactIdentity-test.js | 71 ++++++++++++++++++++++++ src/utils/mapChildren.js | 2 +- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 5c42a8e86f..bc3769df92 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -72,7 +72,7 @@ function isEmptyChild(child) { function assignKey(setKey, child, index) { if (ReactComponent.isValidComponent(child)) { - var key = child._key || child.props.key; + var key = child.props.key; if (__DEV__) { if (!HAS_WARNED && !key) { HAS_WARNED = true; @@ -300,7 +300,7 @@ var ReactComponent = { } else if (!isEmptyChild(child)) { - if (ReactComponent.isValidComponent(child) && !child._key) { + if (ReactComponent.isValidComponent(child)) { // This is a static node and therefore safe to key by index. // No warning necessary. child._key = child.props.key || ('' + (i - 1)); diff --git a/src/core/__tests__/ReactIdentity-test.js b/src/core/__tests__/ReactIdentity-test.js index ecf71a8553..e21be9d3bb 100644 --- a/src/core/__tests__/ReactIdentity-test.js +++ b/src/core/__tests__/ReactIdentity-test.js @@ -80,4 +80,75 @@ describe('ReactIdentity', function() { .toEqual('.reactRoot[0].:2.:chipmunk'); }); + it('should let restructured components retain their uniqueness', function() { + var instance0 = ; + var instance1 = ; + var instance2 = ; + var wrapped =
{instance0} {instance1}
; + var unwrappedAndAdded = +
+ {instance2} + {wrapped.props.children[0]} + {wrapped.props.children[1]} +
; + + expect(function() { + + React.renderComponent(unwrappedAndAdded, document.createElement('div')); + + }).not.toThrow(); + }); + + it('should retain keys during updates in composite components', function() { + + var TestComponent = React.createClass({ + render: function() { + return
{this.props.children}
; + } + }); + + var TestContainer = React.createClass({ + + getInitialState: function() { + return { swapped: false }; + }, + + swap: function() { + this.setState({ swapped: true }); + }, + + render: function() { + return ( + + {this.state.swapped ? this.props.second : this.props.first} + {this.state.swapped ? this.props.first : this.props.second} + + ); + } + + }); + + var instance0 = ; + var instance1 = ; + + var wrapped = ; + + React.renderComponent(wrapped, document.createElement('div')); + + var beforeKey = wrapped + ._renderedComponent + ._renderedComponent + .props.children[0]._key; + + wrapped.swap(); + + var afterKey = wrapped + ._renderedComponent + ._renderedComponent + .props.children[0]._key; + + expect(beforeKey).not.toEqual(afterKey); + + }); + }); diff --git a/src/utils/mapChildren.js b/src/utils/mapChildren.js index cdba3e4d90..27079e8930 100644 --- a/src/utils/mapChildren.js +++ b/src/utils/mapChildren.js @@ -26,7 +26,7 @@ function mapChildren(children, mapFunction, context) { var child = children[ii]; var key = child._key; var mappedChild = mapFunction.call(context, child, key, ii); - mappedChild._key = key; + mappedChild.props.key = key; mappedChildren.push(mappedChild); } return mappedChildren;