Updated ReactCompositeComponent to trigger componentWillReceiveProps when either props or context change. Fixes issue #5756

This commit is contained in:
Miles Johnson
2016-01-06 15:49:47 -08:00
parent 2751e8179d
commit f4c5b2c00b
2 changed files with 134 additions and 10 deletions
@@ -647,12 +647,18 @@ var ReactCompositeComponentMixin = {
nextUnmaskedContext
) {
var inst = this._instance;
var nextContext = this._context === nextUnmaskedContext ?
inst.context :
this._processContext(nextUnmaskedContext);
var willReceive = false;
var nextContext;
var nextProps;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
// Distinguish between a props update versus a simple state update
if (prevParentElement === nextParentElement) {
// Skip checking prop types again -- we don't read inst.props to avoid
@@ -660,13 +666,14 @@ var ReactCompositeComponentMixin = {
nextProps = nextParentElement.props;
} else {
nextProps = this._processProps(nextParentElement.props);
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
willReceive = true;
}
if (inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
var nextState = this._processPendingState(nextProps, nextContext);
@@ -874,6 +874,123 @@ describe('ReactCompositeComponent', function() {
expect(div.children[0].id).toBe('aliens');
});
it('should trigger componentWillReceiveProps for context changes', function() {
var contextChanges = 0;
var propChanges = 0;
var GrandChild = React.createClass({
contextTypes: {
foo: ReactPropTypes.string.isRequired,
},
componentWillReceiveProps: function(nextProps, nextContext) {
expect('foo' in nextContext).toBe(true);
if (nextProps !== this.props) {
propChanges++;
}
if (nextContext !== this.context) {
contextChanges++;
}
},
render: function() {
return <span className="grand-child">{this.props.children}</span>;
},
});
var ChildWithContext = React.createClass({
contextTypes: {
foo: ReactPropTypes.string.isRequired,
},
componentWillReceiveProps: function(nextProps, nextContext) {
expect('foo' in nextContext).toBe(true);
if (nextProps !== this.props) {
propChanges++;
}
if (nextContext !== this.context) {
contextChanges++;
}
},
render: function() {
return <div className="child-with">{this.props.children}</div>;
},
});
var ChildWithoutContext = React.createClass({
componentWillReceiveProps: function(nextProps, nextContext) {
expect('foo' in nextContext).toBe(false);
if (nextProps !== this.props) {
propChanges++;
}
if (nextContext !== this.context) {
contextChanges++;
}
},
render: function() {
return <div className="child-without">{this.props.children}</div>;
},
});
var Parent = React.createClass({
childContextTypes: {
foo: ReactPropTypes.string,
},
getInitialState() {
return {
foo: 'abc',
};
},
getChildContext: function() {
return {
foo: this.state.foo,
};
},
onClick() {
this.setState({
foo: 'def',
});
},
render: function() {
return <div className="parent" onClick={this.onClick}>{this.props.children}</div>;
},
});
var div = document.createElement('div');
ReactDOM.render(
<Parent>
<ChildWithoutContext>
A1
<GrandChild>A2</GrandChild>
</ChildWithoutContext>
<ChildWithContext>
B1
<GrandChild>B2</GrandChild>
</ChildWithContext>
</Parent>,
div
);
ReactTestUtils.Simulate.click(div.childNodes[0]);
expect(propChanges).toBe(0);
expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
});
it('should disallow nested render calls', function() {
var Inner = React.createClass({
render: function() {