diff --git a/src/addons/__tests__/renderSubtreeIntoContainer-test.js b/src/addons/__tests__/renderSubtreeIntoContainer-test.js index 7898af3d7fba8..dcb70481a7fe6 100644 --- a/src/addons/__tests__/renderSubtreeIntoContainer-test.js +++ b/src/addons/__tests__/renderSubtreeIntoContainer-test.js @@ -12,13 +12,13 @@ 'use strict'; var React = require('React'); +var ReactDOM = require('ReactDOM'); var ReactTestUtils = require('ReactTestUtils'); var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer'); describe('renderSubtreeIntoContainer', function() { it('should pass context when rendering subtree elsewhere', function() { - var portal = document.createElement('div'); var Component = React.createClass({ @@ -92,4 +92,107 @@ describe('renderSubtreeIntoContainer', function() { }, }); }); + + it('should update context if it changes due to setState', function() { + var container = document.createElement('div'); + document.body.appendChild(container); + var portal = document.createElement('div'); + + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string.isRequired, + getFoo: React.PropTypes.func.isRequired, + }, + + render: function() { + return
{this.context.foo + '-' + this.context.getFoo()}
; + }, + }); + + var Parent = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + getFoo: React.PropTypes.func.isRequired, + }, + + getChildContext: function() { + return { + foo: this.state.bar, + getFoo: () => this.state.bar, + }; + }, + + getInitialState: function() { + return { + bar: 'initial', + }; + }, + + render: function() { + return null; + }, + + componentDidMount: function() { + renderSubtreeIntoContainer(this, , portal); + }, + + componentDidUpdate() { + renderSubtreeIntoContainer(this, , portal); + }, + }); + + var instance = ReactDOM.render(, container); + expect(portal.firstChild.innerHTML).toBe('initial-initial'); + instance.setState({bar: 'changed'}); + expect(portal.firstChild.innerHTML).toBe('changed-changed'); + }); + + it('should update context if it changes due to re-render', function() { + var container = document.createElement('div'); + document.body.appendChild(container); + var portal = document.createElement('div'); + + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string.isRequired, + getFoo: React.PropTypes.func.isRequired, + }, + + render: function() { + return
{this.context.foo + '-' + this.context.getFoo()}
; + }, + }); + + var Parent = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + getFoo: React.PropTypes.func.isRequired, + }, + + getChildContext: function() { + return { + foo: this.props.bar, + getFoo: () => this.props.bar, + }; + }, + + render: function() { + return null; + }, + + componentDidMount: function() { + renderSubtreeIntoContainer(this, , portal); + }, + + componentDidUpdate() { + renderSubtreeIntoContainer(this, , portal); + }, + }); + + ReactDOM.render(, container); + expect(portal.firstChild.innerHTML).toBe('initial-initial'); + ReactDOM.render(, container); + expect(portal.firstChild.innerHTML).toBe('changed-changed'); + }); + }); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 5e1827b746519..d7fb1b561f835 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -20,6 +20,7 @@ var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactElement = require('ReactElement'); var ReactFeatureFlags = require('ReactFeatureFlags'); +var ReactInstanceMap = require('ReactInstanceMap'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactReconciler = require('ReactReconciler'); @@ -287,10 +288,11 @@ var ReactMount = { _updateRootComponent: function( prevComponent, nextElement, + nextContext, container, callback) { ReactMount.scrollMonitor(container, function() { - ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement, nextContext); if (callback) { ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); } @@ -389,7 +391,7 @@ var ReactMount = { */ renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { invariant( - parentComponent != null && parentComponent._reactInternalInstance != null, + parentComponent != null && ReactInstanceMap.has(parentComponent), 'parentComponent must be a valid React Component' ); return ReactMount._renderSubtreeIntoContainer( @@ -440,6 +442,14 @@ var ReactMount = { nextElement ); + var nextContext; + if (parentComponent) { + var parentInst = ReactInstanceMap.get(parentComponent); + nextContext = parentInst._processChildContext(parentInst._context); + } else { + nextContext = emptyObject; + } + var prevComponent = getTopLevelWrapperInContainer(container); if (prevComponent) { @@ -453,6 +463,7 @@ var ReactMount = { ReactMount._updateRootComponent( prevComponent, nextWrappedElement, + nextContext, container, updatedCallback ); @@ -501,11 +512,7 @@ var ReactMount = { nextWrappedElement, container, shouldReuseMarkup, - parentComponent != null ? - parentComponent._reactInternalInstance._processChildContext( - parentComponent._reactInternalInstance._context - ) : - emptyObject + nextContext )._renderedComponent.getPublicInstance(); if (callback) { callback.call(component); diff --git a/src/renderers/native/ReactNativeMount.js b/src/renderers/native/ReactNativeMount.js index 8fc575d97f930..d9fe2f28623fe 100644 --- a/src/renderers/native/ReactNativeMount.js +++ b/src/renderers/native/ReactNativeMount.js @@ -118,7 +118,7 @@ var ReactNativeMount = { var prevWrappedElement = prevComponent._currentElement; var prevElement = prevWrappedElement.props; if (shouldUpdateReactComponent(prevElement, nextElement)) { - ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement); + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement, emptyObject); if (callback) { ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); } diff --git a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js index 94369f43348da..9d4071cc3ffe9 100644 --- a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js @@ -246,8 +246,10 @@ var ReactUpdateQueue = { enqueueUpdate(internalInstance); }, - enqueueElementInternal: function(internalInstance, newElement) { - internalInstance._pendingElement = newElement; + enqueueElementInternal: function(internalInstance, nextElement, nextContext) { + internalInstance._pendingElement = nextElement; + // TODO: introduce _pendingContext instead of setting it directly. + internalInstance._context = nextContext; enqueueUpdate(internalInstance); },