diff --git a/src/addons/__tests__/renderSubtreeIntoContainer.js b/src/addons/__tests__/renderSubtreeIntoContainer.js new file mode 100644 index 0000000000..80de6145cf --- /dev/null +++ b/src/addons/__tests__/renderSubtreeIntoContainer.js @@ -0,0 +1,96 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React = require('React'); +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({ + contextTypes: { + foo: React.PropTypes.string.isRequired + }, + + render: function() { + return
{this.context.foo}
; + } + }); + + var Parent = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + }, + + getChildContext: function() { + return { + foo: 'bar' + }; + }, + + render: function() { + return null; + }, + + componentDidMount: function() { + expect(function() { + renderSubtreeIntoContainer(this, , portal); + }.bind(this)).not.toThrow(); + } + }); + + ReactTestUtils.renderIntoDocument(); + expect(portal.firstChild.innerHTML).toBe('bar'); + }); + + it('should throw if parentComponent is invalid', function () { + var portal = document.createElement('div'); + + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string.isRequired + }, + + render: function() { + return
{this.context.foo}
; + } + }); + + var Parent = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + }, + + getChildContext: function() { + return { + foo: 'bar' + }; + }, + + render: function() { + return null; + }, + + componentDidMount: function() { + expect(function() { + renderSubtreeIntoContainer(, , portal); + }).toThrow('Invariant Violation: parentComponent' + + 'must be a valid React Component'); + } + }); + }); +}); diff --git a/src/addons/renderSubtreeIntoContainer.js b/src/addons/renderSubtreeIntoContainer.js new file mode 100644 index 0000000000..e145f6e4a1 --- /dev/null +++ b/src/addons/renderSubtreeIntoContainer.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * +* @providesModule renderSubtreeIntoContainer +*/ + +'use strict'; + +var ReactMount = require('ReactMount'); + +module.exports = ReactMount.renderSubtreeIntoContainer; diff --git a/src/browser/ReactWithAddons.js b/src/browser/ReactWithAddons.js index 02c3ca7005..bcf5f06cea 100644 --- a/src/browser/ReactWithAddons.js +++ b/src/browser/ReactWithAddons.js @@ -28,6 +28,7 @@ var ReactTransitionGroup = require('ReactTransitionGroup'); var ReactUpdates = require('ReactUpdates'); var cloneWithProps = require('cloneWithProps'); +var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer'); var shallowCompare = require('shallowCompare'); var update = require('update'); @@ -40,6 +41,7 @@ React.addons = { batchedUpdates: ReactUpdates.batchedUpdates, cloneWithProps: cloneWithProps, createFragment: ReactFragment.create, + renderSubtreeIntoContainer: renderSubtreeIntoContainer, shallowCompare: shallowCompare, update: update }; diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index 9e64fd86d0..a7afef275f 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -262,10 +262,12 @@ function mountComponentIntoNode( rootID, container, transaction, - shouldReuseMarkup) { - var context = emptyObject; + shouldReuseMarkup, + context) { if (__DEV__) { - context = {}; + if (context === emptyObject) { + context = {}; + } context[validateDOMNesting.tagStackContextKey] = [container.nodeName.toLowerCase()]; } @@ -288,7 +290,8 @@ function batchedMountComponentIntoNode( componentInstance, rootID, container, - shouldReuseMarkup) { + shouldReuseMarkup, + context) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform( mountComponentIntoNode, @@ -297,7 +300,8 @@ function batchedMountComponentIntoNode( rootID, container, transaction, - shouldReuseMarkup + shouldReuseMarkup, + context ); ReactUpdates.ReactReconcileTransaction.release(transaction); } @@ -402,7 +406,8 @@ var ReactMount = { _renderNewRootComponent: function( nextElement, container, - shouldReuseMarkup + shouldReuseMarkup, + context ) { // Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested; @@ -432,7 +437,8 @@ var ReactMount = { componentInstance, reactRootID, container, - shouldReuseMarkup + shouldReuseMarkup, + context ); if (__DEV__) { @@ -451,12 +457,26 @@ var ReactMount = { * perform an update on it and only mutate the DOM as necessary to reflect the * latest React component. * + * @param {ReactComponent} parentComponent The conceptual parent of this render tree. * @param {ReactElement} nextElement Component element to render. * @param {DOMElement} container DOM element to render into. * @param {?function} callback function triggered on completion * @return {ReactComponent} Component instance rendered in `container`. */ - render: function(nextElement, container, callback) { + renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { + invariant( + parentComponent != null && parentComponent._reactInternalInstance != null, + 'parentComponent must be a valid React Component' + ); + return ReactMount._renderSubtreeIntoContainer( + parentComponent, + nextElement, + container, + callback + ); + }, + + _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { invariant( ReactElement.isValidElement(nextElement), 'React.render(): Invalid component element.%s', @@ -524,11 +544,15 @@ var ReactMount = { } var shouldReuseMarkup = containerHasReactMarkup && !prevComponent; - var component = ReactMount._renderNewRootComponent( nextElement, container, - shouldReuseMarkup + shouldReuseMarkup, + parentComponent != null ? + parentComponent._reactInternalInstance._processChildContext( + parentComponent._reactInternalInstance._context + ) : + emptyObject ).getPublicInstance(); if (callback) { callback.call(component); @@ -536,6 +560,23 @@ var ReactMount = { return component; }, + + /** + * Renders a React component into the DOM in the supplied `container`. + * + * If the React component was previously rendered into `container`, this will + * perform an update on it and only mutate the DOM as necessary to reflect the + * latest React component. + * + * @param {ReactElement} nextElement Component element to render. + * @param {DOMElement} container DOM element to render into. + * @param {?function} callback function triggered on completion + * @return {ReactComponent} Component instance rendered in `container`. + */ + render: function(nextElement, container, callback) { + return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback); + }, + /** * Constructs a component instance of `constructor` with `initialProps` and * renders it into the supplied `container`. diff --git a/src/core/ReactDefaultBatchingStrategy.js b/src/core/ReactDefaultBatchingStrategy.js index 6083799d6e..de856c7762 100644 --- a/src/core/ReactDefaultBatchingStrategy.js +++ b/src/core/ReactDefaultBatchingStrategy.js @@ -54,16 +54,16 @@ var ReactDefaultBatchingStrategy = { * Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. */ - batchedUpdates: function(callback, a, b, c, d) { + batchedUpdates: function(callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // The code is written this way to avoid extra allocations if (alreadyBatchingUpdates) { - callback(a, b, c, d); + callback(a, b, c, d, e); } else { - transaction.perform(callback, null, a, b, c, d); + transaction.perform(callback, null, a, b, c, d, e); } } }; diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index 1c7aea1a13..29ca26641e 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -105,9 +105,9 @@ assign( PooledClass.addPoolingTo(ReactUpdatesFlushTransaction); -function batchedUpdates(callback, a, b, c, d) { +function batchedUpdates(callback, a, b, c, d, e) { ensureInjected(); - batchingStrategy.batchedUpdates(callback, a, b, c, d); + batchingStrategy.batchedUpdates(callback, a, b, c, d, e); } /**