diff --git a/src/browser/ReactTextComponent.js b/src/browser/ReactTextComponent.js index c8080dc233..f1e71151af 100644 --- a/src/browser/ReactTextComponent.js +++ b/src/browser/ReactTextComponent.js @@ -45,6 +45,16 @@ var ReactTextComponent = function(initialText) { this.construct({text: initialText}); }; +/** + * Used to clone the text descriptor object before it's mounted. + * + * @param {object} props + * @return {object} A new ReactTextComponent instance + */ +ReactTextComponent.ConvenienceConstructor = function(props) { + return new ReactTextComponent(props.text); +}; + mixInto(ReactTextComponent, ReactComponent.Mixin); mixInto(ReactTextComponent, ReactBrowserComponentMixin); mixInto(ReactTextComponent, { diff --git a/src/browser/server/ReactServerRendering.js b/src/browser/server/ReactServerRendering.js index 5dc221c710..056eca4eac 100644 --- a/src/browser/server/ReactServerRendering.js +++ b/src/browser/server/ReactServerRendering.js @@ -24,7 +24,7 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); - +var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); /** @@ -49,7 +49,8 @@ function renderComponentToString(component) { transaction = ReactServerRenderingTransaction.getPooled(false); return transaction.perform(function() { - var markup = component.mountComponent(id, transaction, 0); + var componentInstance = instantiateReactComponent(component); + var markup = componentInstance.mountComponent(id, transaction, 0); return ReactMarkupChecksum.addChecksumToMarkup(markup); }, null); } finally { diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index ccc993f5a1..f98409abb5 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -25,6 +25,7 @@ var ReactPerf = require('ReactPerf'); var containsNode = require('containsNode'); var getReactRootElementInContainer = require('getReactRootElementInContainer'); +var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); @@ -296,8 +297,13 @@ var ReactMount = { nextComponent, container, shouldReuseMarkup) { - var reactRootID = ReactMount._registerComponent(nextComponent, container); - nextComponent.mountComponentIntoNode( + + var componentInstance = instantiateReactComponent(nextComponent); + var reactRootID = ReactMount._registerComponent( + componentInstance, + container + ); + componentInstance.mountComponentIntoNode( reactRootID, container, shouldReuseMarkup @@ -309,7 +315,7 @@ var ReactMount = { getReactRootElementInContainer(container); } - return nextComponent; + return componentInstance; } ), diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index efc8f8d2ef..ffade81194 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -29,6 +29,7 @@ var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactUpdates = require('ReactUpdates'); +var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); var keyMirror = require('keyMirror'); var merge = require('merge'); @@ -595,19 +596,52 @@ if (__DEV__) { constructor: true, construct: true, isOwnedBy: true, // should be deprecated but can have code mod (internal) - mountComponent: true, - mountComponentIntoNode: true, - props: true, type: true, - _checkPropTypes: true, - _mountComponentIntoNode: true, - _processContext: true + props: true, + // currently private but belong on the descriptor and are valid for use + // inside the framework: + __keyValidated__: true, + _owner: true, + _currentContext: true + }; + + var componentInstanceProperties = { + __keyValidated__: true, + __keySetters: true, + _compositeLifeCycleState: true, + _currentContext: true, + _defaultProps: true, + _instance: true, + _lifeCycleState: true, + _mountDepth: true, + _owner: true, + _pendingCallbacks: true, + _pendingContext: true, + _pendingForceUpdate: true, + _pendingOwner: true, + _pendingProps: true, + _pendingState: true, + _renderedComponent: true, + _rootNodeID: true, + context: true, + props: true, + refs: true, + state: true, + + // These are known instance properties coming from other sources + _pendingQueries: true, + _queryPropListeners: true, + queryParams: true + }; var hasWarnedOnComponentType = {}; - var warnIfUnmounted = function(instance, key) { - if (instance.__hasBeenMounted) { + var warningStackCounter = 0; + + var issueMembraneWarning = function(instance, key) { + var isWhitelisted = unmountedPropertyWhitelist.hasOwnProperty(key); + if (warningStackCounter > 0 || isWhitelisted) { return; } var name = instance.constructor.displayName || 'Unknown'; @@ -631,6 +665,30 @@ if (__DEV__) { ); }; + var wrapInMembraneFunction = function(fn, thisBinding) { + if (fn.__reactMembraneFunction && fn.__reactMembraneSelf === thisBinding) { + return fn.__reactMembraneFunction; + } + return fn.__reactMembraneFunction = function() { + /** + * By getting this function, you've already received a warning. The + * internals of this function will likely cause more warnings. To avoid + * Spamming too much we disable any warning triggered inside of this + * stack. + */ + warningStackCounter++; + try { + // If the this binding is unchanged, we defer to the real component. + // This is important to keep some referential integrity in the + // internals. E.g. owner equality check. + var self = this === thisBinding ? this.__realComponentInstance : this; + return fn.apply(self, arguments); + } finally { + warningStackCounter--; + } + }; + }; + var defineMembraneProperty = function(membrane, prototype, key) { Object.defineProperty(membrane, key, { @@ -638,31 +696,33 @@ if (__DEV__) { enumerable: true, get: function() { - if (this !== membrane) { - // When this is accessed through a prototype chain we need to check if - // this component was mounted. - warnIfUnmounted(this, key); + if (this === membrane) { + // We're allowed to access the prototype directly. + return prototype[key]; } - return prototype[key]; + issueMembraneWarning(this, key); + + var realValue = this.__realComponentInstance[key]; + // If the real value is a function, we need to provide a wrapper that + // disables nested warnings. The properties type and constructors are + // expected to the be constructors and therefore is often use with an + // equality check and we shouldn't try to rebind those. + if (typeof realValue === 'function' && + key !== 'type' && + key !== 'constructor') { + return wrapInMembraneFunction(realValue, this); + } + return realValue; }, set: function(value) { - if (this !== membrane) { - // When this is accessed through a prototype chain, we first check if - // this component was mounted. Then we define a value on "this" - // instance, effectively disabling the membrane on that prototype - // chain. - warnIfUnmounted(this, key); - Object.defineProperty(this, key, { - enumerable: true, - configurable: true, - writable: true, - value: value - }); - } else { - // Otherwise, this should modify the prototype + if (this === membrane) { + // We're allowed to set a value on the prototype directly. prototype[key] = value; + return; } + issueMembraneWarning(this, key); + this.__realComponentInstance[key] = value; } }); @@ -677,26 +737,51 @@ if (__DEV__) { * @private */ var createMountWarningMembrane = function(prototype) { - try { - var membrane = Object.create(prototype); - for (var key in prototype) { - if (unmountedPropertyWhitelist.hasOwnProperty(key)) { - continue; - } + var membrane = {}; + var key; + for (key in prototype) { + defineMembraneProperty(membrane, prototype, key); + } + // These are properties that goes into the instance but not the prototype. + // We can create the membrane on the prototype even though this will + // result in a faulty hasOwnProperty check it's better perf. + for (key in componentInstanceProperties) { + if (componentInstanceProperties.hasOwnProperty(key) && + !(key in prototype)) { defineMembraneProperty(membrane, prototype, key); } + } + return membrane; + }; - membrane.mountComponent = function() { - this.__hasBeenMounted = true; - return prototype.mountComponent.apply(this, arguments); + /** + * Creates a membrane constructor which wraps the component that gets mounted. + * + * @param {function} constructor Original constructor. + * @return {function} The membrane constructor. + * @private + */ + var createDescriptorProxy = function(constructor) { + try { + var ProxyConstructor = function() { + this.__realComponentInstance = new constructor(); + + // We can only safely pass through known instance variables. Unknown + // expandos are not safe. Use the real mounted instance to avoid this + // problem if it blows something up. + Object.freeze(this); }; - return membrane; + ProxyConstructor.prototype = createMountWarningMembrane( + constructor.prototype + ); + + return ProxyConstructor; } catch(x) { // In IE8 define property will fail on non-DOM objects. If anything in - // the membrane creation fails, we'll bail out and just use the prototype - // without warnings. - return prototype; + // the membrane creation fails, we'll bail out and just use the plain + // constructor without warnings. + return constructor; } }; @@ -772,13 +857,29 @@ var ReactCompositeComponentMixin = { this.state = null; this._pendingState = null; - this.context = this._processContext(ReactContext.current); + this.context = null; this._currentContext = ReactContext.current; this._pendingContext = null; + // The descriptor that was used to instantiate this component. Will be + // set by the instantiator instead of the constructor since this + // constructor is currently used by both instances and descriptors. + this._descriptor = null; + this._compositeLifeCycleState = null; }, + /** + * Components in the intermediate state now has cyclic references. To avoid + * breaking JSON serialization we expose a custom JSON format. + * @return {object} JSON compatible representation. + * @internal + * @final + */ + toJSON: function() { + return { type: this.type, props: this.props }; + }, + /** * Checks whether or not this composite component is mounted. * @return {boolean} True if mounted, false otherwise. @@ -812,6 +913,7 @@ var ReactCompositeComponentMixin = { ); this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; + this.context = this._processContext(this._currentContext); this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null; this.props = this._processProps(this.props); @@ -839,7 +941,9 @@ var ReactCompositeComponentMixin = { } } - this._renderedComponent = this._renderValidatedComponent(); + this._renderedComponent = instantiateReactComponent( + this._renderValidatedComponent() + ); // Done with mounting, `setState` will now trigger UI changes. this._compositeLifeCycleState = null; @@ -1173,13 +1277,16 @@ var ReactCompositeComponentMixin = { }, receiveComponent: function(nextComponent, transaction) { - if (nextComponent === this) { + if (nextComponent === this._descriptor) { // Since props and context are immutable after the component is // mounted, we can do a cheap identity compare here to determine // if this is a superfluous reconcile. return; } + // Update the descriptor that was last used by this component instance + this._descriptor = nextComponent; + this._pendingContext = nextComponent._currentContext; ReactComponent.Mixin.receiveComponent.call( this, @@ -1212,17 +1319,19 @@ var ReactCompositeComponentMixin = { prevProps, prevOwner ); - var prevComponent = this._renderedComponent; + + + var prevComponentInstance = this._renderedComponent; var nextComponent = this._renderValidatedComponent(); - if (shouldUpdateReactComponent(prevComponent, nextComponent)) { - prevComponent.receiveComponent(nextComponent, transaction); + if (shouldUpdateReactComponent(prevComponentInstance, nextComponent)) { + prevComponentInstance.receiveComponent(nextComponent, transaction); } else { // These two IDs are actually the same! But nothing should rely on that. var thisID = this._rootNodeID; - var prevComponentID = prevComponent._rootNodeID; - prevComponent.unmountComponent(); - this._renderedComponent = nextComponent; - var nextMarkup = nextComponent.mountComponent( + var prevComponentID = prevComponentInstance._rootNodeID; + prevComponentInstance.unmountComponent(); + this._renderedComponent = instantiateReactComponent(nextComponent); + var nextMarkup = this._renderedComponent.mountComponent( thisID, transaction, this._mountDepth + 1 @@ -1401,10 +1510,12 @@ var ReactCompositeComponent = { Constructor.prototype = new ReactCompositeComponentBase(); Constructor.prototype.constructor = Constructor; + var DescriptorConstructor = Constructor; + var ConvenienceConstructor = function(props, children) { - var instance = new Constructor(); - instance.construct.apply(instance, arguments); - return instance; + var descriptor = new DescriptorConstructor(); + descriptor.construct.apply(descriptor, arguments); + return descriptor; }; ConvenienceConstructor.componentConstructor = Constructor; Constructor.ConvenienceConstructor = ConvenienceConstructor; @@ -1452,7 +1563,10 @@ var ReactCompositeComponent = { } if (__DEV__) { - Constructor.prototype = createMountWarningMembrane(Constructor.prototype); + // In DEV the convenience constructor generates a proxy to another + // instance around it to warn about access to properties on the + // descriptor. + DescriptorConstructor = createDescriptorProxy(Constructor); } return ConvenienceConstructor; diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js index 22a54b7b5b..8cc6c10106 100644 --- a/src/core/ReactMultiChild.js +++ b/src/core/ReactMultiChild.js @@ -23,6 +23,7 @@ var ReactComponent = require('ReactComponent'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var flattenChildren = require('flattenChildren'); +var instantiateReactComponent = require('instantiateReactComponent'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); /** @@ -192,14 +193,18 @@ var ReactMultiChild = { for (var name in children) { var child = children[name]; if (children.hasOwnProperty(name)) { + // The rendered children must be turned into instances as they're + // mounted. + var childInstance = instantiateReactComponent(child); + children[name] = childInstance; // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; - var mountImage = child.mountComponent( + var mountImage = childInstance.mountComponent( rootID, transaction, this._mountDepth + 1 ); - child._mountIndex = index; + childInstance._mountIndex = index; mountImages.push(mountImage); index++; } @@ -293,8 +298,10 @@ var ReactMultiChild = { lastIndex = Math.max(prevChild._mountIndex, lastIndex); this._unmountChildByName(prevChild, name); } + // The child must be instantiated before it's mounted. + var nextChildInstance = instantiateReactComponent(nextChild); this._mountChildByNameAtIndex( - nextChild, name, nextIndex, transaction + nextChildInstance, name, nextIndex, transaction ); } nextIndex++; diff --git a/src/core/__tests__/ReactBind-test.js b/src/core/__tests__/ReactBind-test.js index 00743a04f6..ef5b93c3b2 100644 --- a/src/core/__tests__/ReactBind-test.js +++ b/src/core/__tests__/ReactBind-test.js @@ -61,13 +61,13 @@ describe('autobinding', function() { }); var instance1 = ; - ReactTestUtils.renderIntoDocument(instance1); + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); var rendered1 = reactComponentExpect(instance1) .expectRenderedChild() .instance(); var instance2 = ; - ReactTestUtils.renderIntoDocument(instance2); + var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2); var rendered2 = reactComponentExpect(instance2) .expectRenderedChild() .instance(); @@ -77,25 +77,25 @@ describe('autobinding', function() { badIdea(); }).toThrow(); - expect(instance1.onMouseEnter).toBe(instance2.onMouseEnter); - expect(instance1.onMouseLeave).toBe(instance2.onMouseLeave); - expect(instance1.onClick).not.toBe(instance2.onClick); + expect(mountedInstance1.onMouseEnter).toBe(mountedInstance2.onMouseEnter); + expect(mountedInstance1.onMouseLeave).toBe(mountedInstance2.onMouseLeave); + expect(mountedInstance1.onClick).not.toBe(mountedInstance2.onClick); ReactTestUtils.Simulate.click(rendered1); expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); ReactTestUtils.Simulate.click(rendered2); expect(mouseDidClick.mock.instances.length).toBe(2); - expect(mouseDidClick.mock.instances[1]).toBe(instance2); + expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2); ReactTestUtils.Simulate.mouseOver(rendered1); expect(mouseDidEnter.mock.instances.length).toBe(1); - expect(mouseDidEnter.mock.instances[0]).toBe(instance1); + expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1); ReactTestUtils.Simulate.mouseOver(rendered2); expect(mouseDidEnter.mock.instances.length).toBe(2); - expect(mouseDidEnter.mock.instances[1]).toBe(instance2); + expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2); ReactTestUtils.Simulate.mouseOut(rendered1); expect(mouseDidLeave.mock.instances.length).toBe(1); @@ -122,14 +122,14 @@ describe('autobinding', function() { }); var instance1 = ; - ReactTestUtils.renderIntoDocument(instance1); + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); var rendered1 = reactComponentExpect(instance1) .expectRenderedChild() .instance(); ReactTestUtils.Simulate.click(rendered1); expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); }); }); diff --git a/src/core/__tests__/ReactComponentLifeCycle-test.js b/src/core/__tests__/ReactComponentLifeCycle-test.js index a2163e826e..ae35b942ee 100644 --- a/src/core/__tests__/ReactComponentLifeCycle-test.js +++ b/src/core/__tests__/ReactComponentLifeCycle-test.js @@ -312,9 +312,7 @@ describe('ReactComponentLifeCycle', function() { // A component that is merely "constructed" (as in "constructor") but not // yet initialized, or rendered. // - var instance = ; - expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED); - ReactTestUtils.renderIntoDocument(instance); + var instance = ReactTestUtils.renderIntoDocument(); // getInitialState expect(instance._testJournal.returnedFromGetInitialState).toEqual( diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index d6141e9d48..f42b0054f9 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -183,21 +183,23 @@ describe('ReactCompositeComponent', function() { // Next, prove that once mounted, the scope is bound correctly to the actual // component. - ReactTestUtils.renderIntoDocument(instance); + var mountedInstance = ReactTestUtils.renderIntoDocument(instance); expect(console.warn.argsForCall.length).toBe(3); + // This will result in a warning that has already been issued before. var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance); - expect(console.warn.argsForCall.length).toBe(4); + expect(console.warn.argsForCall.length).toBe(3); var autoBound = instance.methodAutoBound; var explicitlyNotBound = instance.methodExplicitlyNotBound; var context = {}; - expect(explicitlyBound.call(context)).toBe(instance); - expect(autoBound.call(context)).toBe(instance); + expect(explicitlyBound.call(context)).toBe(mountedInstance); + expect(autoBound.call(context)).toBe(mountedInstance); expect(explicitlyNotBound.call(context)).toBe(context); - expect(explicitlyBound.call(instance)).toBe(instance); - expect(autoBound.call(instance)).toBe(instance); - expect(explicitlyNotBound.call(instance)).toBe(instance); + expect(explicitlyBound.call(instance)).toBe(mountedInstance); + expect(autoBound.call(instance)).toBe(mountedInstance); + // This one is the weird one + expect(explicitlyNotBound.call(instance)).toBe(mountedInstance); }); @@ -279,8 +281,7 @@ describe('ReactCompositeComponent', function() { } }); - var instance = ; - ReactTestUtils.renderIntoDocument(instance); + var instance = ReactTestUtils.renderIntoDocument(); reactComponentExpect(instance).scalarPropsEqual({key: 'testKey'}); reactComponentExpect(instance).scalarStateEqual({key: 'testKeyState'}); @@ -1179,9 +1180,8 @@ describe('ReactCompositeComponent', function() { expect(console.warn.argsForCall.length).toBe(0); var unmountedInstance = ; - var result = unmountedInstance.someMethod(); + unmountedInstance.someMethod(); expect(console.warn.argsForCall.length).toBe(1); - expect(result).toBe(unmountedInstance); var unmountedInstance2 = ; unmountedInstance2.someOtherMethod = 'override'; diff --git a/src/core/instantiateReactComponent.js b/src/core/instantiateReactComponent.js new file mode 100644 index 0000000000..750b13fda8 --- /dev/null +++ b/src/core/instantiateReactComponent.js @@ -0,0 +1,70 @@ +/** + * Copyright 2013-2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule instantiateReactComponent + * @typechecks static-only + */ + +"use strict"; + +var warning = require('warning'); + +/** + * Validate a `componentDescriptor`. This should be exposed publicly in a follow + * up diff. + * + * @param {object} descriptor + * @return {boolean} Returns true if this is a valid descriptor of a Component. + */ +function isValidComponentDescriptor(descriptor) { + return ( + typeof descriptor.constructor === 'function' && + typeof descriptor.constructor.prototype.construct === 'function' && + typeof descriptor.constructor.prototype.mountComponent === 'function' && + typeof descriptor.constructor.prototype.receiveComponent === 'function' + ); +} + +/** + * Given a `componentDescriptor` create an instance that will actually be + * mounted. Currently it just extracts an existing clone from composite + * components but this is an implementation detail which will change. + * + * @param {object} descriptor + * @return {object} A new instance of componentDescriptor's constructor. + * @protected + */ +function instantiateReactComponent(descriptor) { + if (__DEV__) { + warning( + isValidComponentDescriptor(descriptor), + 'Only React Components are valid for mounting.' + ); + // We use the clone of a composite component instead of the original + // instance. This allows us to warn you if you're are accessing the wrong + // instance. + var instance = descriptor.__realComponentInstance || descriptor; + instance._descriptor = descriptor; + return instance; + } + // In prod we don't clone, we simply use the same instance for unaffected + // behavior. We have to keep the descriptor around for comparison later on. + // This should ideally be accepted in the constructor of the instance but + // since that is currently overloaded, we just manually attach it here. + descriptor._descriptor = descriptor; + return descriptor; +} + +module.exports = instantiateReactComponent; diff --git a/src/core/shouldUpdateReactComponent.js b/src/core/shouldUpdateReactComponent.js index 07781cc59a..c2ee9a4030 100644 --- a/src/core/shouldUpdateReactComponent.js +++ b/src/core/shouldUpdateReactComponent.js @@ -20,33 +20,36 @@ "use strict"; /** - * Given a `prevComponent` and `nextComponent`, determines if `prevComponent` - * should be updated as opposed to being destroyed or replaced. + * Given a `prevComponentInstance` and `nextComponent`, determines if + * `prevComponentInstance` should be updated as opposed to being destroyed or + * replaced by a new instance. The second argument is a descriptor. Future + * versions of the reconciler should only compare descriptors to other + * descriptors. * - * @param {?object} prevComponent - * @param {?object} nextComponent - * @return {boolean} True if `prevComponent` should be updated. + * @param {?object} prevComponentInstance + * @param {?object} nextDescriptor + * @return {boolean} True if `prevComponentInstance` should be updated. * @protected */ -function shouldUpdateReactComponent(prevComponent, nextComponent) { +function shouldUpdateReactComponent(prevComponentInstance, nextDescriptor) { // TODO: Remove warning after a release. - if (prevComponent && nextComponent && - prevComponent.constructor === nextComponent.constructor && ( - (prevComponent.props && prevComponent.props.key) === - (nextComponent.props && nextComponent.props.key) + if (prevComponentInstance && nextDescriptor && + prevComponentInstance.constructor === nextDescriptor.constructor && ( + (prevComponentInstance.props && prevComponentInstance.props.key) === + (nextDescriptor.props && nextDescriptor.props.key) )) { - if (prevComponent._owner === nextComponent._owner) { + if (prevComponentInstance._owner === nextDescriptor._owner) { return true; } else { if (__DEV__) { - if (prevComponent.state) { + if (prevComponentInstance.state) { console.warn( 'A recent change to React has been found to impact your code. ' + 'A mounted component will now be unmounted and replaced by a ' + 'component (of the same class) if their owners are different. ' + 'Previously, ownership was not considered when updating.', - prevComponent, - nextComponent + prevComponentInstance, + nextDescriptor ); } } diff --git a/src/utils/traverseAllChildren.js b/src/utils/traverseAllChildren.js index 2fa6861f34..379d6e98f1 100644 --- a/src/utils/traverseAllChildren.js +++ b/src/utils/traverseAllChildren.js @@ -126,7 +126,8 @@ var traverseAllChildrenImpl = // All of the above are perceived as null. callback(traverseContext, null, storageName, indexSoFar); subtreeCount = 1; - } else if (children.mountComponentIntoNode) { + } else if (children.type && children.type.prototype && + children.type.prototype.mountComponentIntoNode) { callback(traverseContext, children, storageName, indexSoFar); subtreeCount = 1; } else {