diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index f2f321426a..df365ea911 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -723,6 +723,12 @@ var ReactCompositeComponentMixin = assign({}, nextContext = this._processContext(nextParentElement._context); nextProps = this._processProps(nextParentElement.props); + if (__DEV__) { + if (nextUnmaskedContext != null) { + this._warnIfContextsDiffer(nextParentElement._context, nextUnmaskedContext); + } + } + this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; if (inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); @@ -808,7 +814,7 @@ var ReactCompositeComponentMixin = assign({}, inst.state = nextState; inst.context = nextContext; - this._updateRenderedComponent(transaction, nextContext); + this._updateRenderedComponent(transaction, unmaskedContext); if (inst.componentDidUpdate) { transaction.getReactMountReady().enqueue( diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 5122601b10..4cb128842d 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -579,7 +579,14 @@ describe('ReactCompositeComponent', function() { ReactTestUtils.renderIntoDocument(); }); + // Two warnings, one for the component and one for the div + // We may want to make this expect one warning in the future expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[0][0]).toBe( + 'Warning: owner based context (keys: foo) does not equal parent based ' + + 'context (keys: ) while mounting Component ' + + '(see: http://fb.me/react-context-by-parent)' + ); expect(console.warn.mock.calls[1][0]).toBe( 'Warning: owner based context (keys: foo) does not equal parent based ' + 'context (keys: ) while mounting ReactCompositeComponent ' + @@ -620,15 +627,212 @@ describe('ReactCompositeComponent', function() { ReactTestUtils.renderIntoDocument({component}); + // Two warnings, one for the component and one for the div + // We may want to make this expect one warning in the future expect(console.warn.mock.calls.length).toBe(2); expect(console.warn.mock.calls[0][0]).toBe( 'Warning: owner-based and parent-based contexts differ ' + '(values: `noise` vs `bar`) for key (foo) while mounting Component ' + '(see: http://fb.me/react-context-by-parent)' ); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: owner-based and parent-based contexts differ ' + + '(values: `noise` vs `bar`) for key (foo) while mounting ReactCompositeComponent ' + + '(see: http://fb.me/react-context-by-parent)' + ); }); + it('should warn if context values differ on update using withContext', function() { + var Parent = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string + }, + + getChildContext: function() { + return { + foo: "bar" + }; + }, + + render: function() { + return
{this.props.children}
; + } + }); + + var Component = React.createClass({ + contextTypes: { + foo: ReactPropTypes.string.isRequired + }, + + render: function() { + return
; + } + }); + + var div = document.createElement('div'); + + var componentWithSameContext = React.withContext({foo: 'bar'}, function() { + return ; + }); + React.render({componentWithSameContext}, div); + + expect(console.warn.mock.calls.length).toBe(0); + + var componentWithDifferentContext = React.withContext({foo: 'noise'}, function() { + return ; + }); + React.render({componentWithDifferentContext}, div); + + // Two warnings, one for the component and one for the div + // We may want to make this expect one warning in the future + expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[0][0]).toBe( + 'Warning: owner-based and parent-based contexts differ ' + + '(values: `noise` vs `bar`) for key (foo) while mounting Component ' + + '(see: http://fb.me/react-context-by-parent)' + ); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: owner-based and parent-based contexts differ ' + + '(values: `noise` vs `bar`) for key (foo) while mounting ReactCompositeComponent ' + + '(see: http://fb.me/react-context-by-parent)' + ); + + }); + + it('should warn if context values differ on update using wrapper', function() { + var Parent = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string + }, + + getChildContext: function() { + return { + foo: "bar" + }; + }, + + render: function() { + return
{this.props.children}
; + } + }); + + var Component = React.createClass({ + contextTypes: { + foo: ReactPropTypes.string.isRequired + }, + + render: function() { + return
; + } + }); + + var Wrapper = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string + }, + + getChildContext: function() { + return { foo: this.props.foo }; + }, + + render: function() { return ; } + + }); + + var div = document.createElement('div'); + React.render(, div); + React.render(, div); + + // Two warnings, one for the component and one for the div + // We may want to make this expect one warning in the future + expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[0][0]).toBe( + 'Warning: owner-based and parent-based contexts differ ' + + '(values: `noise` vs `bar`) for key (foo) while mounting Component ' + + '(see: http://fb.me/react-context-by-parent)' + ); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: owner-based and parent-based contexts differ ' + + '(values: `noise` vs `bar`) for key (foo) while mounting ReactCompositeComponent ' + + '(see: http://fb.me/react-context-by-parent)' + ); + + }); + + it('unmasked context propagates through updates', function() { + + var Leaf = React.createClass({ + contextTypes: { + foo: ReactPropTypes.string.isRequired + }, + + componentWillReceiveProps: function(nextProps, nextContext) { + expect('foo' in nextContext).toBe(true); + }, + + componentDidUpdate: function(prevProps, prevState, prevContext) { + expect('foo' in prevContext).toBe(true); + }, + + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + expect('foo' in nextContext).toBe(true); + return true; + }, + + render: function() { + return {this.context.foo}; + } + }); + + var Intermediary = React.createClass({ + + componentWillReceiveProps: function(nextProps, nextContext) { + expect('foo' in nextContext).toBe(false); + }, + + componentDidUpdate: function(prevProps, prevState, prevContext) { + expect('foo' in prevContext).toBe(false); + }, + + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + expect('foo' in nextContext).toBe(false); + return true; + }, + + render: function() { + return ; + } + }); + + var Parent = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string + }, + + getChildContext: function() { + return { + foo: this.props.cntxt + }; + }, + + render: function() { + return ; + } + }); + + var div = document.createElement('div'); + React.render(, div); + expect(div.children[0].innerHTML).toBe('noise'); + div.children[0].innerHTML = 'aliens'; + div.children[0].id = 'aliens'; + expect(div.children[0].innerHTML).toBe('aliens'); + expect(div.children[0].id).toBe('aliens'); + React.render(, div); + expect(div.children[0].innerHTML).toBe('bar'); + expect(div.children[0].id).toBe('aliens'); + }); + it('should disallow nested render calls', function() { spyOn(console, 'warn'); var Inner = React.createClass({