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.
This commit is contained in:
CommitSyncScript
2013-06-06 14:29:44 -07:00
committed by Paul O’Shannessy
parent 54d3134da2
commit b581c8cfc7
3 changed files with 74 additions and 3 deletions
+2 -2
View File
@@ -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));
+71
View File
@@ -80,4 +80,75 @@ describe('ReactIdentity', function() {
.toEqual('.reactRoot[0].:2.:chipmunk');
});
it('should let restructured components retain their uniqueness', function() {
var instance0 = <span />;
var instance1 = <span />;
var instance2 = <span />;
var wrapped = <div>{instance0} {instance1}</div>;
var unwrappedAndAdded =
<div>
{instance2}
{wrapped.props.children[0]}
{wrapped.props.children[1]}
</div>;
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 <div>{this.props.children}</div>;
}
});
var TestContainer = React.createClass({
getInitialState: function() {
return { swapped: false };
},
swap: function() {
this.setState({ swapped: true });
},
render: function() {
return (
<TestComponent>
{this.state.swapped ? this.props.second : this.props.first}
{this.state.swapped ? this.props.first : this.props.second}
</TestComponent>
);
}
});
var instance0 = <span key="A" />;
var instance1 = <span key="B" />;
var wrapped = <TestContainer first={instance0} second={instance1} />;
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);
});
});
+1 -1
View File
@@ -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;