From 382f399f096eebb305c36a2335415a1dd8b8f3f3 Mon Sep 17 00:00:00 2001 From: Jim Date: Mon, 10 Nov 2014 15:02:47 -0800 Subject: [PATCH] Fixed all unit tests for issue #2112 --- src/browser/ReactTextComponent.js | 5 +- src/browser/server/ReactServerRendering.js | 13 ++- src/browser/ui/ReactDOMComponent.js | 36 +++++--- src/browser/ui/ReactMount.js | 5 +- .../ui/__tests__/ReactDOMComponent-test.js | 4 +- src/browser/ui/dom/__tests__/Danger-test.js | 6 +- src/core/ReactComponent.js | 38 +++++++-- src/core/ReactCompositeComponent.js | 82 +++++++++++++------ src/core/ReactContext.js | 3 +- src/core/ReactMultiChild.js | 30 +++++-- src/core/ReactUpdates.js | 4 +- .../__tests__/ReactCompositeComponent-test.js | 74 +++++++++++++---- src/core/__tests__/ReactElement-test.js | 8 +- 13 files changed, 217 insertions(+), 91 deletions(-) diff --git a/src/browser/ReactTextComponent.js b/src/browser/ReactTextComponent.js index 87503773912da..d259ffa033ca9 100644 --- a/src/browser/ReactTextComponent.js +++ b/src/browser/ReactTextComponent.js @@ -51,12 +51,13 @@ assign(ReactTextComponent.prototype, ReactComponent.Mixin, { * @return {string} Markup for this text node. * @internal */ - mountComponent: function(rootID, transaction, mountDepth) { + mountComponent: function(rootID, transaction, mountDepth, context) { ReactComponent.Mixin.mountComponent.call( this, rootID, transaction, - mountDepth + mountDepth, + context ); var escapedText = escapeTextForBrowser(this.props); diff --git a/src/browser/server/ReactServerRendering.js b/src/browser/server/ReactServerRendering.js index b0d9e5897f970..d29989e0cb710 100644 --- a/src/browser/server/ReactServerRendering.js +++ b/src/browser/server/ReactServerRendering.js @@ -17,14 +17,17 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); +var emptyObject = require('emptyObject'); var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); /** * @param {ReactElement} element + * @param {?object} context * @return {string} the HTML markup */ -function renderToString(element) { +function renderToString(element, context) { + if(context === undefined) context = emptyObject; invariant( ReactElement.isValidElement(element), 'renderToString(): You must pass a valid ReactElement.' @@ -37,7 +40,7 @@ function renderToString(element) { return transaction.perform(function() { var componentInstance = instantiateReactComponent(element, null); - var markup = componentInstance.mountComponent(id, transaction, 0); + var markup = componentInstance.mountComponent(id, transaction, 0, context); return ReactMarkupChecksum.addChecksumToMarkup(markup); }, null); } finally { @@ -47,10 +50,12 @@ function renderToString(element) { /** * @param {ReactElement} element + * @param {?object} context * @return {string} the HTML markup, without the extra React ID and checksum * (for generating static pages) */ -function renderToStaticMarkup(element) { +function renderToStaticMarkup(element, context) { + if(context === undefined) context = emptyObject; invariant( ReactElement.isValidElement(element), 'renderToStaticMarkup(): You must pass a valid ReactElement.' @@ -63,7 +68,7 @@ function renderToStaticMarkup(element) { return transaction.perform(function() { var componentInstance = instantiateReactComponent(element, null); - return componentInstance.mountComponent(id, transaction, 0); + return componentInstance.mountComponent(id, transaction, 0, context); }, null); } finally { ReactServerRenderingTransaction.release(transaction); diff --git a/src/browser/ui/ReactDOMComponent.js b/src/browser/ui/ReactDOMComponent.js index 71f80ecfbb564..2dc77699358e8 100644 --- a/src/browser/ui/ReactDOMComponent.js +++ b/src/browser/ui/ReactDOMComponent.js @@ -168,18 +168,20 @@ ReactDOMComponent.Mixin = { mountComponent: ReactPerf.measure( 'ReactDOMComponent', 'mountComponent', - function(rootID, transaction, mountDepth) { + function(rootID, transaction, mountDepth, context) { + invariant(context !== undefined, "Context is required parameter"); ReactComponent.Mixin.mountComponent.call( this, rootID, transaction, - mountDepth + mountDepth, + context ); assertValidProps(this.props); var closeTag = omittedCloseTags[this._tag] ? '' : ''; return ( this._createOpenTagMarkupAndPutListeners(transaction) + - this._createContentMarkup(transaction) + + this._createContentMarkup(transaction, context) + closeTag ); } @@ -241,9 +243,10 @@ ReactDOMComponent.Mixin = { * * @private * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + * @param {object} context * @return {string} Content markup. */ - _createContentMarkup: function(transaction) { + _createContentMarkup: function(transaction, context) { var prefix = ''; if (this._tag === 'listing' || this._tag === 'pre' || @@ -269,7 +272,8 @@ ReactDOMComponent.Mixin = { } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, - transaction + transaction, + context ); return prefix + mountImages.join(''); } @@ -277,7 +281,8 @@ ReactDOMComponent.Mixin = { return prefix; }, - receiveComponent: function(nextElement, transaction) { + receiveComponent: function(nextElement, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); if (nextElement === this._currentElement && nextElement._owner != null) { // Since elements are immutable after the owner is rendered, @@ -293,7 +298,8 @@ ReactDOMComponent.Mixin = { ReactComponent.Mixin.receiveComponent.call( this, nextElement, - transaction + transaction, + context ); }, @@ -310,16 +316,19 @@ ReactDOMComponent.Mixin = { updateComponent: ReactPerf.measure( 'ReactDOMComponent', 'updateComponent', - function(transaction, prevElement, nextElement) { + function(transaction, prevElement, nextElement, context) { + if(context === undefined) throw new Error("Context required for mounting"); + if(context === null) context = this._context; assertValidProps(this._currentElement.props); ReactComponent.Mixin.updateComponent.call( this, transaction, prevElement, - nextElement + nextElement, + context ); this._updateDOMProperties(prevElement.props, transaction); - this._updateDOMChildren(prevElement.props, transaction); + this._updateDOMChildren(prevElement.props, transaction, context); } ), @@ -425,7 +434,8 @@ ReactDOMComponent.Mixin = { * @param {object} lastProps * @param {ReactReconcileTransaction} transaction */ - _updateDOMChildren: function(lastProps, transaction) { + _updateDOMChildren: function(lastProps, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); var nextProps = this.props; var lastContent = @@ -449,7 +459,7 @@ ReactDOMComponent.Mixin = { var lastHasContentOrHtml = lastContent != null || lastHtml != null; var nextHasContentOrHtml = nextContent != null || nextHtml != null; if (lastChildren != null && nextChildren == null) { - this.updateChildren(null, transaction); + this.updateChildren(null, transaction, context); } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { this.updateTextContent(''); } @@ -466,7 +476,7 @@ ReactDOMComponent.Mixin = { ); } } else if (nextChildren != null) { - this.updateChildren(nextChildren, transaction); + this.updateChildren(nextChildren, transaction, context); } }, diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index b62e56080a969..927b36779847a 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -20,6 +20,7 @@ var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactPerf = require('ReactPerf'); +var emptyObject = require('emptyObject'); var containsNode = require('containsNode'); var deprecated = require('deprecated'); var getReactRootElementInContainer = require('getReactRootElementInContainer'); @@ -327,10 +328,12 @@ var ReactMount = { componentInstance, container ); + componentInstance.mountComponentIntoNode( reactRootID, container, - shouldReuseMarkup + shouldReuseMarkup, + emptyObject ); if (__DEV__) { diff --git a/src/browser/ui/__tests__/ReactDOMComponent-test.js b/src/browser/ui/__tests__/ReactDOMComponent-test.js index 1257e11640477..d3612769aedaf 100644 --- a/src/browser/ui/__tests__/ReactDOMComponent-test.js +++ b/src/browser/ui/__tests__/ReactDOMComponent-test.js @@ -281,7 +281,7 @@ describe('ReactDOMComponent', function() { genMarkup = function(props) { var transaction = new ReactReconcileTransaction(); - return (new NodeStub(props))._createContentMarkup(transaction); + return (new NodeStub(props))._createContentMarkup(transaction, {}); }; this.addMatchers({ @@ -326,7 +326,7 @@ describe('ReactDOMComponent', function() { _owner: null, _context: null }); - return stubComponent.mountComponent('test', transaction, 0); + return stubComponent.mountComponent('test', transaction, 0, {}); }; }); diff --git a/src/browser/ui/dom/__tests__/Danger-test.js b/src/browser/ui/dom/__tests__/Danger-test.js index 78f7760b7cb15..1e2e6f02d338e 100644 --- a/src/browser/ui/dom/__tests__/Danger-test.js +++ b/src/browser/ui/dom/__tests__/Danger-test.js @@ -32,7 +32,7 @@ describe('Danger', function() { it('should render markup', function() { var markup = instantiateReactComponent(
- ).mountComponent('.rX', transaction, 0); + ).mountComponent('.rX', transaction, 0, {}); var output = Danger.dangerouslyRenderMarkup([markup])[0]; expect(output.nodeName).toBe('DIV'); @@ -44,7 +44,7 @@ describe('Danger', function() { ).mountComponent( '.rX', transaction, - 0 + 0, {} ); var output = Danger.dangerouslyRenderMarkup([markup])[0]; @@ -55,7 +55,7 @@ describe('Danger', function() { it('should render wrapped markup', function() { var markup = instantiateReactComponent( - ).mountComponent('.rX', transaction, 0); + ).mountComponent('.rX', transaction, 0, {}); var output = Danger.dangerouslyRenderMarkup([markup])[0]; expect(output.nodeName).toBe('TH'); diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 2e400d3e201ec..4161f83224e06 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -238,6 +238,10 @@ var ReactComponent = { // All components start unmounted. this._lifeCycleState = ComponentLifeCycle.UNMOUNTED; + // Object/map containing variables defined in the mount context. + // Value is null iff ComponentLifeCycle.UNMOUNTED + this._context = null; + // See ReactUpdates. this._pendingCallbacks = null; @@ -245,6 +249,7 @@ var ReactComponent = { // to track updates. this._currentElement = element; this._pendingElement = null; + this._pendingContext = null; }, /** @@ -261,7 +266,8 @@ var ReactComponent = { * @return {?string} Rendered markup to be inserted into the DOM. * @internal */ - mountComponent: function(rootID, transaction, mountDepth) { + mountComponent: function(rootID, transaction, mountDepth, context) { + invariant(context !== undefined, "Context is required parameter"); invariant( !this.isMounted(), 'mountComponent(%s, ...): Can only mount an unmounted component. ' + @@ -276,6 +282,7 @@ var ReactComponent = { } this._rootNodeID = rootID; this._lifeCycleState = ComponentLifeCycle.MOUNTED; + this._context = context; this._mountDepth = mountDepth; // Effectively: return ''; }, @@ -302,6 +309,7 @@ var ReactComponent = { unmountIDFromEnvironment(this._rootNodeID); this._rootNodeID = null; this._lifeCycleState = ComponentLifeCycle.UNMOUNTED; + this._context = null; }, /** @@ -315,13 +323,15 @@ var ReactComponent = { * @param {ReactReconcileTransaction} transaction * @internal */ - receiveComponent: function(nextElement, transaction) { + receiveComponent: function(nextElement, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); invariant( this.isMounted(), 'receiveComponent(...): Can only update a mounted component.' ); this._pendingElement = nextElement; - this.performUpdateIfNecessary(transaction); + this._context = context; + this.performUpdateIfNecessary(transaction, context); }, /** @@ -336,11 +346,12 @@ var ReactComponent = { } var prevElement = this._currentElement; var nextElement = this._pendingElement; + var nextContext = this._pendingContext; this._currentElement = nextElement; this.props = nextElement.props; this._owner = nextElement._owner; this._pendingElement = null; - this.updateComponent(transaction, prevElement, nextElement); + this.updateComponent(transaction, prevElement, nextElement, nextContext); }, /** @@ -351,7 +362,8 @@ var ReactComponent = { * @param {object} nextElement * @internal */ - updateComponent: function(transaction, prevElement, nextElement) { + updateComponent: function(transaction, prevElement, nextElement, context) { + invariant(context !== undefined, "Context is required parameter"); // If either the owner or a `ref` has changed, make sure the newest owner // has stored a reference to `this`, and the previous owner (if different) // has forgotten the reference to `this`. We use the element instead @@ -386,7 +398,12 @@ var ReactComponent = { * @internal * @see {ReactMount.render} */ - mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) { + mountComponentIntoNode: function( + rootID, + container, + shouldReuseMarkup, + context) { + invariant(context !== undefined, "Context is required parameter"); var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform( this._mountComponentIntoNode, @@ -394,7 +411,8 @@ var ReactComponent = { rootID, container, transaction, - shouldReuseMarkup + shouldReuseMarkup, + context ); ReactUpdates.ReactReconcileTransaction.release(transaction); }, @@ -411,8 +429,10 @@ var ReactComponent = { rootID, container, transaction, - shouldReuseMarkup) { - var markup = this.mountComponent(rootID, transaction, 0); + shouldReuseMarkup, + context) { + invariant(context !== undefined, "Context is required parameter"); + var markup = this.mountComponent(rootID, transaction, 0, context); mountImageIntoNode(markup, container, shouldReuseMarkup); }, diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index b78909c6bd126..9f822553881c0 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -22,6 +22,7 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactUpdates = require('ReactUpdates'); +var emptyObject = require('emptyObject'); var assign = require('Object.assign'); var invariant = require('invariant'); var keyMirror = require('keyMirror'); @@ -148,12 +149,14 @@ var ReactCompositeComponentMixin = assign({}, mountComponent: ReactPerf.measure( 'ReactCompositeComponent', 'mountComponent', - function(rootID, transaction, mountDepth) { + function(rootID, transaction, mountDepth, context) { + invariant(context !== undefined, "Context is required parameter"); ReactComponent.Mixin.mountComponent.call( this, rootID, transaction, - mountDepth + mountDepth, + context ); var inst = this._instance; @@ -164,6 +167,7 @@ var ReactCompositeComponentMixin = assign({}, this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; inst.context = this._processContext(this._currentElement._context); + this._warnIfContextsDiffer(this._currentElement._context, context); inst.props = this._processProps(this._currentElement.props); var initialState = inst.getInitialState ? inst.getInitialState() : null; @@ -206,7 +210,8 @@ var ReactCompositeComponentMixin = assign({}, var markup = this._renderedComponent.mountComponent( rootID, transaction, - mountDepth + 1 + mountDepth + 1, + this._processChildContext(context) ); if (inst.componentDidMount) { transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); @@ -329,18 +334,17 @@ var ReactCompositeComponentMixin = assign({}, _processContext: function(context) { var maskedContext = null; var contextTypes = this._instance.constructor.contextTypes; - if (contextTypes) { - maskedContext = {}; - for (var contextName in contextTypes) { - maskedContext[contextName] = context[contextName]; - } - if (__DEV__) { - this._checkPropTypes( - contextTypes, - maskedContext, - ReactPropTypeLocations.context - ); - } + if (!contextTypes) return emptyObject; + maskedContext = {}; + for (var contextName in contextTypes) { + maskedContext[contextName] = context[contextName]; + } + if (__DEV__) { + this._checkPropTypes( + contextTypes, + maskedContext, + ReactPropTypeLocations.context + ); } return maskedContext; }, @@ -428,7 +432,8 @@ var ReactCompositeComponentMixin = assign({}, } }, - receiveComponent: function(nextElement, transaction) { + receiveComponent: function(nextElement, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); if (nextElement === this._currentElement && nextElement._owner != null) { // Since elements are immutable after the owner is rendered, @@ -444,7 +449,8 @@ var ReactCompositeComponentMixin = assign({}, ReactComponent.Mixin.receiveComponent.call( this, nextElement, - transaction + transaction, + context ); }, @@ -455,7 +461,8 @@ var ReactCompositeComponentMixin = assign({}, * @param {ReactReconcileTransaction} transaction * @internal */ - performUpdateIfNecessary: function(transaction) { + performUpdateIfNecessary: function(transaction, context) { + invariant(context !== undefined, "Context is required parameter"); var compositeLifeCycleState = this._compositeLifeCycleState; // Do not trigger a state transition if we are in the middle of mounting or // receiving props because both of those will already be doing this. @@ -480,10 +487,30 @@ var ReactCompositeComponentMixin = assign({}, this.updateComponent( transaction, prevElement, - nextElement + nextElement, + context ); }, + /** + * Compare two contexts, warning if they are different + * TODO: Remove this check when owner-context is removed + */ + _warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) { + invariant(ownerBasedContext !== undefined, "Owner based context is required parameter"); + invariant(parentBasedContext !== undefined, "Parent based ontext is required parameter"); + var ownerKeys = Object.keys(ownerBasedContext).sort(); + var parentKeys = Object.keys(parentBasedContext).sort(); + if (ownerKeys.length != parentKeys.length || ownerKeys.toString() != parentKeys.toString()) { + var message = ("owner based context (keys: " + + Object.keys(ownerBasedContext) + ") does not equal parent based" + + " context (keys: "+Object.keys(parentBasedContext)+")" + + " while mounting " + + (this._instance.constructor.displayName || 'ReactCompositeComponent')); + console.warn(message); + } + }, + /** * Perform an update to a mounted component. The componentWillReceiveProps and * shouldComponentUpdate methods are called, then (assuming the update isn't @@ -502,13 +529,15 @@ var ReactCompositeComponentMixin = assign({}, updateComponent: ReactPerf.measure( 'ReactCompositeComponent', 'updateComponent', - function(transaction, prevParentElement, nextParentElement) { + function(transaction, prevParentElement, nextParentElement, context) { + invariant(context !== undefined, "Context is required parameter"); // Update refs regardless of what shouldComponentUpdate returns ReactComponent.Mixin.updateComponent.call( this, transaction, prevParentElement, - nextParentElement + nextParentElement, + context ); var inst = this._instance; @@ -611,7 +640,7 @@ var ReactCompositeComponentMixin = assign({}, // it. TODO: Remove this._owner completely. this._owner = nextElement._owner; - this._updateRenderedComponent(transaction); + this._updateRenderedComponent(transaction, nextContext); if (inst.componentDidUpdate) { transaction.getReactMountReady().enqueue( @@ -627,14 +656,16 @@ var ReactCompositeComponentMixin = assign({}, * @param {ReactReconcileTransaction} transaction * @internal */ - _updateRenderedComponent: function(transaction) { + _updateRenderedComponent: function(transaction, context) { + invariant(context !== undefined, "Context is required parameter"); var prevComponentInstance = this._renderedComponent; var prevRenderedElement = prevComponentInstance._currentElement; var nextRenderedElement = this._renderValidatedComponent(); if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { prevComponentInstance.receiveComponent( nextRenderedElement, - transaction + transaction, + context ); } else { // These two IDs are actually the same! But nothing should rely on that. @@ -648,7 +679,8 @@ var ReactCompositeComponentMixin = assign({}, var nextMarkup = this._renderedComponent.mountComponent( thisID, transaction, - this._mountDepth + 1 + this._mountDepth + 1, + context ); ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID( prevComponentID, diff --git a/src/core/ReactContext.js b/src/core/ReactContext.js index 55c94e1bf5dfb..449796988e20b 100644 --- a/src/core/ReactContext.js +++ b/src/core/ReactContext.js @@ -12,6 +12,7 @@ "use strict"; var assign = require('Object.assign'); +var emptyObject = require('emptyObject'); var monitorCodeUse = require('monitorCodeUse'); /** @@ -26,7 +27,7 @@ var ReactContext = { * @internal * @type {object} */ - current: {}, + current: emptyObject, /** * Temporarily extends the current context while executing scopedCallback. diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js index 7e57ae3db9998..4569e71d6e51b 100644 --- a/src/core/ReactMultiChild.js +++ b/src/core/ReactMultiChild.js @@ -17,6 +17,7 @@ var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var flattenChildren = require('flattenChildren'); var instantiateReactComponent = require('instantiateReactComponent'); +var invariant = require('invariant'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); /** @@ -178,7 +179,8 @@ var ReactMultiChild = { * @return {array} An array of mounted representations. * @internal */ - mountChildren: function(nestedChildren, transaction) { + mountChildren: function(nestedChildren, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); var children = flattenChildren(nestedChildren); var mountImages = []; var index = 0; @@ -195,7 +197,8 @@ var ReactMultiChild = { var mountImage = childInstance.mountComponent( rootID, transaction, - this._mountDepth + 1 + this._mountDepth + 1, + context ); childInstance._mountIndex = index; mountImages.push(mountImage); @@ -240,11 +243,12 @@ var ReactMultiChild = { * @param {ReactReconcileTransaction} transaction * @internal */ - updateChildren: function(nextNestedChildren, transaction) { + updateChildren: function(nextNestedChildren, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); updateDepth++; var errorThrown = true; try { - this._updateChildren(nextNestedChildren, transaction); + this._updateChildren(nextNestedChildren, transaction, context); errorThrown = false; } finally { updateDepth--; @@ -263,7 +267,8 @@ var ReactMultiChild = { * @final * @protected */ - _updateChildren: function(nextNestedChildren, transaction) { + _updateChildren: function(nextNestedChildren, transaction, context) { + invariant(context !== undefined, "Context is required parameter"); var nextChildren = flattenChildren(nextNestedChildren); var prevChildren = this._renderedChildren; if (!nextChildren && !prevChildren) { @@ -284,7 +289,7 @@ var ReactMultiChild = { if (shouldUpdateReactComponent(prevElement, nextElement)) { this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex); - prevChild.receiveComponent(nextElement, transaction); + prevChild.receiveComponent(nextElement, transaction, context); prevChild._mountIndex = nextIndex; } else { if (prevChild) { @@ -298,7 +303,7 @@ var ReactMultiChild = { null ); this._mountChildByNameAtIndex( - nextChildInstance, name, nextIndex, transaction + nextChildInstance, name, nextIndex, transaction, context ); } nextIndex++; @@ -389,13 +394,20 @@ var ReactMultiChild = { * @param {ReactReconcileTransaction} transaction * @private */ - _mountChildByNameAtIndex: function(child, name, index, transaction) { + _mountChildByNameAtIndex: function( + child, + name, + index, + transaction, + context) { + invariant(context !== undefined, "Context is required parameter"); // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; var mountImage = child.mountComponent( rootID, transaction, - this._mountDepth + 1 + this._mountDepth + 1, + context ); child._mountIndex = index; this.createChild(child, mountImage); diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index 7486e3ac6742b..47cdf9aaf6d9a 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -144,8 +144,10 @@ function runBatchedUpdates(transaction) { // shouldn't execute the callbacks until the next render happens, so // stash the callbacks first var callbacks = component._pendingCallbacks; + var context = component._pendingContext; component._pendingCallbacks = null; - component.performUpdateIfNecessary(transaction.reconcileTransaction); + component._pendingContext = null; + component.performUpdateIfNecessary(transaction.reconcileTransaction, context); if (callbacks) { for (var j = 0; j < callbacks.length; j++) { diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index fbe792596f189..56bce0e191378 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -1128,21 +1128,50 @@ describe('ReactCompositeComponent', function() { 'Warning: Required context `foo` was not specified in `Component`.' ); - React.withContext({foo: 'bar'}, function() { - ReactTestUtils.renderIntoDocument(); + var ComponentInFooStringContext = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string + }, + + getChildContext: function() { + return { + foo: this.props.fooValue + }; + }, + + render: function() { + return ; + } }); + ReactTestUtils.renderIntoDocument(); + // Previous call should not error expect(console.warn.mock.calls.length).toBe(1); - React.withContext({foo: 123}, function() { - ReactTestUtils.renderIntoDocument(); + var ComponentInFooNumberContext = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.number + }, + + getChildContext: function() { + return { + foo: this.props.fooValue + }; + }, + + render: function() { + return ; + } }); + ReactTestUtils.renderIntoDocument(); + expect(console.warn.mock.calls.length).toBe(2); expect(console.warn.mock.calls[1][0]).toBe( 'Warning: Invalid context `foo` of type `number` supplied ' + - 'to `Component`, expected `string`.' + 'to `Component`, expected `string`.' + + ' Check the render method of `ComponentInFooNumberContext`.' ); }); @@ -1163,16 +1192,18 @@ describe('ReactCompositeComponent', function() { }); ReactTestUtils.renderIntoDocument(); - - expect(console.warn.mock.calls.length).toBe(1); + expect(console.warn.mock.calls.length).toBe(2); expect(console.warn.mock.calls[0][0]).toBe( 'Warning: Required child context `foo` was not specified in `Component`.' ); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: Required child context `foo` was not specified in `Component`.' + ); ReactTestUtils.renderIntoDocument(); - expect(console.warn.mock.calls.length).toBe(2); - expect(console.warn.mock.calls[1][0]).toBe( + expect(console.warn.mock.calls.length).toBe(4); + expect(console.warn.mock.calls[3][0]).toBe( 'Warning: Invalid child context `foo` of type `number` ' + 'supplied to `Component`, expected `string`.' ); @@ -1186,7 +1217,7 @@ describe('ReactCompositeComponent', function() { ); // Previous calls should not log errors - expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls.length).toBe(4); }); it('should filter out context not in contextTypes', function() { @@ -1200,11 +1231,26 @@ describe('ReactCompositeComponent', function() { } }); - var instance = React.withContext({foo: 'abc', bar: 123}, function() { - return ; + var ComponentInFooBarContext = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string, + bar: ReactPropTypes.number + }, + + getChildContext: function() { + return { + foo: 'abc', + bar: 123 + }; + }, + + render: function() { + return ; + } }); - instance = ReactTestUtils.renderIntoDocument(instance); - reactComponentExpect(instance).scalarContextEqual({foo: 'abc'}); + + var instance = ReactTestUtils.renderIntoDocument(); + reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'}); }); it('should filter context properly in callbacks', function() { diff --git a/src/core/__tests__/ReactElement-test.js b/src/core/__tests__/ReactElement-test.js index 76255209ddb1a..098e9d52dde28 100644 --- a/src/core/__tests__/ReactElement-test.js +++ b/src/core/__tests__/ReactElement-test.js @@ -97,7 +97,7 @@ describe('ReactElement', function() { ); }); - it('preserves the context on the element', function() { + it('preserves the legacy context on the element', function() { var Component = React.createFactory(ComponentClass); var element; @@ -124,12 +124,6 @@ describe('ReactElement', function() { var element; var Wrapper = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string - }, - getChildContext: function() { - return { foo: 'bar' }; - }, render: function() { element = Component(); return element;