diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 612bf5fd34035..45401f499a82a 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -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); diff --git a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js index 17f8ba963af6c..0bcb65149189a 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js @@ -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 {this.props.children}; + }, + }); + + 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
{this.props.children}
; + }, + }); + + 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
{this.props.children}
; + }, + }); + + 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
{this.props.children}
; + }, + }); + + var div = document.createElement('div'); + + ReactDOM.render( + + + A1 + A2 + + + + B1 + B2 + + , + 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() {