diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index a71b2311d4..5b030c447e 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -22,6 +22,7 @@ var ReactInstanceMap = require('ReactInstanceMap'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); +var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); var emptyObject = require('emptyObject'); @@ -299,9 +300,11 @@ var ReactMount = { ReactElementValidator.checkAndWarnForMutatedProps(nextElement); } - var nextProps = nextElement.props; ReactMount.scrollMonitor(container, function() { - prevComponent.replaceProps(nextProps, callback); + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); + if (callback) { + ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); + } }); if (__DEV__) { diff --git a/src/classic/class/ReactClass.js b/src/classic/class/ReactClass.js index b5359d076b..5b9e5647bf 100644 --- a/src/classic/class/ReactClass.js +++ b/src/classic/class/ReactClass.js @@ -17,6 +17,7 @@ var ReactErrorUtils = require('ReactErrorUtils'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); +var ReactUpdateQueue = require('ReactUpdateQueue'); var assign = require('Object.assign'); var invariant = require('invariant'); @@ -719,16 +720,10 @@ var ReactClassMixin = { * type signature and the only use case for this, is to avoid that. */ replaceState: function(newState, callback) { - var internalInstance = ReactInstanceMap.get(this); - invariant( - internalInstance, - 'replaceState(...): Can only update a mounted or mounting component. ' + - 'This usually means you called replaceState() on an unmounted component.' - ); - internalInstance.replaceState( - newState, - callback - ); + ReactUpdateQueue.enqueueReplaceState(this, newState); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } }, /** @@ -754,16 +749,10 @@ var ReactClassMixin = { * @deprecated */ setProps: function(partialProps, callback) { - var internalInstance = ReactInstanceMap.get(this); - invariant( - internalInstance, - 'setProps(...): Can only update a mounted or mounting component. ' + - 'This usually means you called setProps() on an unmounted component.' - ); - internalInstance.setProps( - partialProps, - callback - ); + ReactUpdateQueue.enqueueSetProps(this, partialProps); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } }, /** @@ -776,10 +765,10 @@ var ReactClassMixin = { * @deprecated */ replaceProps: function(newProps, callback) { - ReactInstanceMap.get(this).replaceProps( - newProps, - callback - ); + ReactUpdateQueue.enqueueReplaceProps(this, newProps); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } } }; diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index 830705a5ed..8d5481a7c5 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -17,6 +17,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); var ReactElementValidator = require('ReactElementValidator'); var ReactInstanceMap = require('ReactInstanceMap'); +var ReactLifeCycle = require('ReactLifeCycle'); var ReactNativeComponent = require('ReactNativeComponent'); var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); @@ -27,8 +28,6 @@ var ReactUpdates = require('ReactUpdates'); var assign = require('Object.assign'); var emptyObject = require('emptyObject'); var invariant = require('invariant'); -var keyMirror = require('keyMirror'); -var monitorCodeUse = require('monitorCodeUse'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var warning = require('warning'); @@ -43,50 +42,6 @@ function getDeclarationErrorAddendum(component) { return ''; } -/** - * `ReactCompositeComponent` maintains an auxiliary life cycle state in - * `this._compositeLifeCycleState` (which can be null). - * - * This is different from the life cycle state maintained by `ReactComponent`. - * The following diagram shows how the states overlap in - * time. There are times when the CompositeLifeCycle is null - at those times it - * is only meaningful to look at ComponentLifeCycle alone. - * - * Top Row: ReactComponent.ComponentLifeCycle - * Low Row: ReactComponent.CompositeLifeCycle - * - * +-------+---------------------------------+--------+ - * | UN | MOUNTED | UN | - * |MOUNTED| | MOUNTED| - * +-------+---------------------------------+--------+ - * | ^--------+ +-------+ +--------^ | - * | | | | | | | | - * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 | - * | | | |PROPS | |MOUNTING| | - * | | | | | | | | - * | | | | | | | | - * | +--------+ +-------+ +--------+ | - * | | | | - * +-------+---------------------------------+--------+ - */ -var CompositeLifeCycle = keyMirror({ - /** - * Components in the process of being mounted respond to state changes - * differently. - */ - MOUNTING: null, - /** - * Components in the process of being unmounted are guarded against state - * changes. - */ - UNMOUNTING: null, - /** - * Components that are mounted and receiving new props respond to state - * changes differently. - */ - RECEIVING_PROPS: null -}); - /** * An incrementing ID assigned to each component when it is mounted. This is * used to enforce the order in which `ReactUpdates` updates dirty components. @@ -112,6 +67,7 @@ var ReactCompositeComponentMixin = { this._rootNodeID = null; this._instance = null; + // See ReactUpdateQueue this._pendingElement = null; this._pendingState = null; this._pendingForceUpdate = false; @@ -123,7 +79,7 @@ var ReactCompositeComponentMixin = { this._mountOrder = 0; this._isTopLevel = false; - // See ReactUpdates. + // See ReactUpdates and ReactUpdateQueue. this._pendingCallbacks = null; }, @@ -134,7 +90,7 @@ var ReactCompositeComponentMixin = { * @final */ isMounted: function() { - return this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING; + return this._compositeLifeCycleState !== ReactLifeCycle.MOUNTING; }, /** @@ -171,7 +127,7 @@ var ReactCompositeComponentMixin = { // Store a reference from the instance back to the internal representation ReactInstanceMap.set(inst, this); - this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; + this._compositeLifeCycleState = ReactLifeCycle.MOUNTING; if (__DEV__) { this._warnIfContextsDiffer(this._currentElement._context, context); @@ -265,7 +221,7 @@ var ReactCompositeComponentMixin = { unmountComponent: function() { var inst = this._instance; - this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; + this._compositeLifeCycleState = ReactLifeCycle.UNMOUNTING; if (inst.componentWillUnmount) { inst.componentWillUnmount(); } @@ -297,50 +253,6 @@ var ReactCompositeComponentMixin = { // TODO: inst.context = null; }, - /** - * Sets a subset of the props. - * - * @param {object} partialProps Subset of the next props. - * @param {?function} callback Called after props are updated. - * @final - * @public - */ - setProps: function(partialProps, callback) { - // Merge with the pending element if it exists, otherwise with existing - // element props. - var element = this._pendingElement || this._currentElement; - this.replaceProps( - assign({}, element.props, partialProps), - callback - ); - }, - - /** - * Replaces all of the props. - * - * @param {object} props New props. - * @param {?function} callback Called after props are updated. - * @final - * @public - */ - replaceProps: function(props, callback) { - invariant( - this._isTopLevel, - 'replaceProps(...): You called `setProps` or `replaceProps` on a ' + - 'component with a parent. This is an anti-pattern since props will ' + - 'get reactively updated when rendered. Instead, change the owner\'s ' + - '`render` method to pass the correct value as props to the component ' + - 'where it is created.' - ); - // This is a deoptimized path. We optimize for always having an element. - // This creates an extra internal element. - this._pendingElement = ReactElement.cloneAndReplaceProps( - this._pendingElement || this._currentElement, - props - ); - ReactUpdates.enqueueUpdate(this, callback); - }, - /** * Schedule a partial update to the props. Only used for internal testing. * @@ -360,109 +272,6 @@ var ReactCompositeComponentMixin = { ReactUpdates.enqueueUpdate(this, callback); }, - /** - * Sets a subset of the state. This only exists because _pendingState is - * internal. This provides a merging strategy that is not available to deep - * properties which is confusing. TODO: Expose pendingState or don't use it - * during the merge. - * - * @param {object} partialState Next partial state to be merged with state. - * @param {?function} callback Called after state is updated. - * @final - * @protected - */ - setState: function(partialState, callback) { - var compositeLifeCycleState = this._compositeLifeCycleState; - invariant( - ReactCurrentOwner.current == null, - 'setState(...): Cannot update during an existing state transition ' + - '(such as within `render`). Render methods should be a pure function ' + - 'of props and state.' - ); - invariant( - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, - 'setState(...): Cannot call setState() on an unmounting component.' - ); - // Merge with `_pendingState` if it exists, otherwise with existing state. - this._pendingState = assign( - {}, - this._pendingState || this._instance.state, - partialState - ); - if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { - // If we're in a componentWillMount handler, don't enqueue a rerender - // because ReactUpdates assumes we're in a browser context (which is wrong - // for server rendering) and we're about to do a render anyway. - // TODO: The callback here is ignored when setState is called from - // componentWillMount. Either fix it or disallow doing so completely in - // favor of getInitialState. - ReactUpdates.enqueueUpdate(this, callback); - } - }, - - /** - * Replaces all of the state. Always use this or `setState` to mutate state. - * You should treat `this.state` as immutable. - * - * There is no guarantee that `this.state` will be immediately updated, so - * accessing `this.state` after calling this method may return the old value. - * - * @param {object} completeState Next state. - * @param {?function} callback Called after state is updated. - * @final - * @protected - */ - replaceState: function(completeState, callback) { - var compositeLifeCycleState = this._compositeLifeCycleState; - invariant( - ReactCurrentOwner.current == null, - 'replaceState(...): Cannot update during an existing state transition ' + - '(such as within `render`). Render methods should be a pure function ' + - 'of props and state.' - ); - invariant( - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, - 'replaceState(...): Cannot call replaceState() on an unmounting ' + - 'component.' - ); - this._pendingState = completeState; - if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { - // If we're in a componentWillMount handler, don't enqueue a rerender - // because ReactUpdates assumes we're in a browser context (which is wrong - // for server rendering) and we're about to do a render anyway. - // TODO: The callback here is ignored when setState is called from - // componentWillMount. Either fix it or disallow doing so completely in - // favor of getInitialState. - ReactUpdates.enqueueUpdate(this, callback); - } - }, - - /** - * Forces an update. This should only be invoked when it is known with - * certainty that we are **not** in a DOM transaction. - * - * You may want to call this when you know that some deeper aspect of the - * component's state has changed but `setState` was not called. - * - * This will not invoke `shouldUpdateComponent`, but it will invoke - * `componentWillUpdate` and `componentDidUpdate`. - * - * @param {?function} callback Called after update is complete.isM - * @final - * @protected - */ - forceUpdate: function(callback) { - var compositeLifeCycleState = this._compositeLifeCycleState; - invariant( - compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE && - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, - 'forceUpdate(...): Cannot force an update while unmounting component ' + - 'or during an existing state transition (such as within `render`).' - ); - this._pendingForceUpdate = true; - ReactUpdates.enqueueUpdate(this, callback); - }, - /** * Filters the context object to only contain keys specified in * `contextTypes` @@ -626,8 +435,8 @@ var ReactCompositeComponentMixin = { 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. - if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || - compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { + if (compositeLifeCycleState === ReactLifeCycle.MOUNTING || + compositeLifeCycleState === ReactLifeCycle.RECEIVING_PROPS) { return; } @@ -743,7 +552,7 @@ var ReactCompositeComponentMixin = { } } - this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; + this._compositeLifeCycleState = ReactLifeCycle.RECEIVING_PROPS; if (inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); } @@ -1003,8 +812,6 @@ ReactPerf.measureMethods( var ReactCompositeComponent = { - LifeCycle: CompositeLifeCycle, - Mixin: ReactCompositeComponentMixin }; diff --git a/src/core/ReactLifeCycle.js b/src/core/ReactLifeCycle.js new file mode 100644 index 0000000000..81ae5c9bbd --- /dev/null +++ b/src/core/ReactLifeCycle.js @@ -0,0 +1,60 @@ +/** + * Copyright 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 ReactLifeCycle + */ + +"use strict"; + +var keyMirror = require('keyMirror'); + +/** + * `ReactCompositeComponent` maintains an auxiliary life cycle state in + * `this._compositeLifeCycleState` (which can be null). + * + * This is different from the life cycle state maintained by `ReactComponent`. + * The following diagram shows how the states overlap in + * time. There are times when the CompositeLifeCycle is null - at those times it + * is only meaningful to look at ComponentLifeCycle alone. + * + * Top Row: ReactComponent.ComponentLifeCycle + * Low Row: ReactComponent.CompositeLifeCycle + * + * +-------+---------------------------------+--------+ + * | UN | MOUNTED | UN | + * |MOUNTED| | MOUNTED| + * +-------+---------------------------------+--------+ + * | ^--------+ +-------+ +--------^ | + * | | | | | | | | + * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 | + * | | | |PROPS | |MOUNTING| | + * | | | | | | | | + * | | | | | | | | + * | +--------+ +-------+ +--------+ | + * | | | | + * +-------+---------------------------------+--------+ + */ +var ReactLifeCycle = keyMirror({ + /** + * Components in the process of being mounted respond to state changes + * differently. + */ + MOUNTING: null, + /** + * Components in the process of being unmounted are guarded against state + * changes. + */ + UNMOUNTING: null, + /** + * Components that are mounted and receiving new props respond to state + * changes differently. + */ + RECEIVING_PROPS: null +}); + +module.exports = ReactLifeCycle; diff --git a/src/core/ReactUpdateQueue.js b/src/core/ReactUpdateQueue.js new file mode 100644 index 0000000000..88fe04b55d --- /dev/null +++ b/src/core/ReactUpdateQueue.js @@ -0,0 +1,264 @@ +/** + * Copyright 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 ReactUpdateQueue + */ + +"use strict"; + +var ReactLifeCycle = require('ReactLifeCycle'); +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactElement = require('ReactElement'); +var ReactInstanceMap = require('ReactInstanceMap'); +var ReactUpdates = require('ReactUpdates'); + +var assign = require('Object.assign'); +var invariant = require('invariant'); + +function enqueueUpdate(internalInstance) { + if (internalInstance._compositeLifeCycleState !== ReactLifeCycle.MOUNTING) { + // If we're in a componentWillMount handler, don't enqueue a rerender + // because ReactUpdates assumes we're in a browser context (which is + // wrong for server rendering) and we're about to do a render anyway. + // See bug in #1740. + ReactUpdates.enqueueUpdate(internalInstance); + } +} + +function getInternalInstanceReadyForUpdate(publicInstance, callerName) { + invariant( + ReactCurrentOwner.current == null, + '%s(...): Cannot update during an existing state transition ' + + '(such as within `render`). Render methods should be a pure function ' + + 'of props and state.', + callerName + ); + + var internalInstance = ReactInstanceMap.get(publicInstance); + invariant( + internalInstance, + '%s(...): Can only update a mounted or mounting component. ' + + 'This usually means you called %s() on an unmounted ' + + 'component.', + callerName, + callerName + ); + invariant( + internalInstance._compositeLifeCycleState !== + ReactLifeCycle.UNMOUNTING, + '%s(...): Cannot call %s() on an unmounting component.', + callerName, + callerName + ); + return internalInstance; +} + +/** + * ReactUpdateQueue allows for state updates to be scheduled into a later + * reconciliation step. + */ +var ReactUpdateQueue = { + + /** + * Enqueue a callback that will be executed after all the pending updates + * have processed. + * + * @param {ReactClass} publicInstance The instance to use as `this` context. + * @param {?function} callback Called after state is updated. + * @internal + */ + enqueueCallback: function(publicInstance, callback) { + invariant( + typeof callback === 'function', + 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' + + 'isn\'t callable.' + ); + var internalInstance = ReactInstanceMap.get(publicInstance); + invariant( + internalInstance, + 'Cannot enqueue a callback on an instance that is unmounted.' + ); + if (internalInstance._compositeLifeCycleState === ReactLifeCycle.MOUNTING) { + // Ignore callbacks in componentWillMount. See enqueueUpdate. + return; + } + if (internalInstance._pendingCallbacks) { + internalInstance._pendingCallbacks.push(callback); + } else { + internalInstance._pendingCallbacks = [callback]; + } + // TODO: The callback here is ignored when setState is called from + // componentWillMount. Either fix it or disallow doing so completely in + // favor of getInitialState. + enqueueUpdate(internalInstance); + }, + + enqueueCallbackInternal: function(internalInstance, callback) { + invariant( + typeof callback === "function", + 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' + + 'isn\'t callable.' + ); + if (internalInstance._pendingCallbacks) { + internalInstance._pendingCallbacks.push(callback); + } else { + internalInstance._pendingCallbacks = [callback]; + } + }, + + /** + * Forces an update. This should only be invoked when it is known with + * certainty that we are **not** in a DOM transaction. + * + * You may want to call this when you know that some deeper aspect of the + * component's state has changed but `setState` was not called. + * + * This will not invoke `shouldUpdateComponent`, but it will invoke + * `componentWillUpdate` and `componentDidUpdate`. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @internal + */ + enqueueForceUpdate: function(publicInstance) { + var internalInstance = getInternalInstanceReadyForUpdate( + publicInstance, + 'forceUpdate' + ); + + internalInstance._pendingForceUpdate = true; + + enqueueUpdate(internalInstance); + }, + + /** + * Replaces all of the state. Always use this or `setState` to mutate state. + * You should treat `this.state` as immutable. + * + * There is no guarantee that `this.state` will be immediately updated, so + * accessing `this.state` after calling this method may return the old value. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} completeState Next state. + * @internal + */ + enqueueReplaceState: function(publicInstance, completeState) { + var internalInstance = getInternalInstanceReadyForUpdate( + publicInstance, + 'replaceState' + ); + + internalInstance._pendingState = completeState; + + enqueueUpdate(internalInstance); + }, + + /** + * Sets a subset of the state. This only exists because _pendingState is + * internal. This provides a merging strategy that is not available to deep + * properties which is confusing. TODO: Expose pendingState or don't use it + * during the merge. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} partialState Next partial state to be merged with state. + * @internal + */ + enqueueSetState: function(publicInstance, partialState) { + var internalInstance = getInternalInstanceReadyForUpdate( + publicInstance, + 'setState' + ); + + // Merge with `_pendingState` if it exists, otherwise with existing state. + internalInstance._pendingState = assign( + {}, + internalInstance._pendingState || internalInstance._instance.state, + partialState + ); + + enqueueUpdate(internalInstance); + }, + + /** + * Sets a subset of the props. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} partialProps Subset of the next props. + * @internal + */ + enqueueSetProps: function(publicInstance, partialProps) { + var internalInstance = getInternalInstanceReadyForUpdate( + publicInstance, + 'setProps' + ); + + invariant( + internalInstance._isTopLevel, + 'setProps(...): You called `setProps` on a ' + + 'component with a parent. This is an anti-pattern since props will ' + + 'get reactively updated when rendered. Instead, change the owner\'s ' + + '`render` method to pass the correct value as props to the component ' + + 'where it is created.' + ); + + // Merge with the pending element if it exists, otherwise with existing + // element props. + var element = internalInstance._pendingElement || + internalInstance._currentElement; + var props = assign({}, element.props, partialProps); + internalInstance._pendingElement = ReactElement.cloneAndReplaceProps( + element, + props + ); + + enqueueUpdate(internalInstance); + }, + + /** + * Replaces all of the props. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} props New props. + * @internal + */ + enqueueReplaceProps: function(publicInstance, props) { + var internalInstance = getInternalInstanceReadyForUpdate( + publicInstance, + 'replaceProps' + ); + + invariant( + internalInstance._isTopLevel, + 'replaceProps(...): You called `replaceProps` on a ' + + 'component with a parent. This is an anti-pattern since props will ' + + 'get reactively updated when rendered. Instead, change the owner\'s ' + + '`render` method to pass the correct value as props to the component ' + + 'where it is created.' + ); + + // Merge with the pending element if it exists, otherwise with existing + // element props. + var element = internalInstance._pendingElement || + internalInstance._currentElement; + internalInstance._pendingElement = ReactElement.cloneAndReplaceProps( + element, + props + ); + + enqueueUpdate(internalInstance); + }, + + enqueueElementInternal: function(internalInstance, newElement) { + internalInstance._pendingElement = newElement; + enqueueUpdate(internalInstance); + } + +}; + +module.exports = ReactUpdateQueue; diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index 7323715d82..dc84254f06 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -190,13 +190,7 @@ flushBatchedUpdates = ReactPerf.measure( * Mark a component as needing a rerender, adding an optional callback to a * list of functions which will be executed once the rerender occurs. */ -function enqueueUpdate(component, callback) { - invariant( - !callback || typeof callback === 'function', - 'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' + - '`setState`, `replaceState`, or `forceUpdate` with a callback that ' + - 'isn\'t callable.' - ); +function enqueueUpdate(component) { ensureInjected(); // Various parts of our code (such as ReactCompositeComponent's @@ -213,19 +207,11 @@ function enqueueUpdate(component, callback) { ); if (!batchingStrategy.isBatchingUpdates) { - batchingStrategy.batchedUpdates(enqueueUpdate, component, callback); + batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } dirtyComponents.push(component); - - if (callback) { - if (component._pendingCallbacks) { - component._pendingCallbacks.push(callback); - } else { - component._pendingCallbacks = [callback]; - } - } } /** diff --git a/src/core/__tests__/ReactComponentLifeCycle-test.js b/src/core/__tests__/ReactComponentLifeCycle-test.js index 936313905c..ab3223ed1e 100644 --- a/src/core/__tests__/ReactComponentLifeCycle-test.js +++ b/src/core/__tests__/ReactComponentLifeCycle-test.js @@ -97,7 +97,7 @@ describe('ReactComponentLifeCycle', function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); ReactCompositeComponent = require('ReactCompositeComponent'); - CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle; + CompositeComponentLifeCycle = require('ReactLifeCycle'); ReactInstanceMap = require('ReactInstanceMap'); @@ -403,11 +403,11 @@ describe('ReactComponentLifeCycle', function() { expect(function() { instance = ReactTestUtils.renderIntoDocument(instance); }).toThrow( - 'Invariant Violation: replaceProps(...): You called `setProps` or ' + - '`replaceProps` on a component with a parent. This is an anti-pattern ' + - 'since props will get reactively updated when rendered. Instead, ' + - 'change the owner\'s `render` method to pass the correct value as ' + - 'props to the component where it is created.' + 'Invariant Violation: setProps(...): You called `setProps` on a ' + + 'component with a parent. This is an anti-pattern since props will get ' + + 'reactively updated when rendered. Instead, change the owner\'s ' + + '`render` method to pass the correct value as props to the component ' + + 'where it is created.' ); }); diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index ee705d4e2f..b51a2db21f 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -294,9 +294,9 @@ describe('ReactCompositeComponent', function() { expect(function() { instance.forceUpdate(); }).toThrow( - 'Invariant Violation: forceUpdate(...): Can only force an update on ' + - 'mounted or mounting components. This usually means you called ' + - 'forceUpdate() on an unmounted component.' + 'Invariant Violation: forceUpdate(...): Can only update a mounted or ' + + 'mounting component. This usually means you called forceUpdate() on ' + + 'an unmounted component.' ); }); @@ -406,11 +406,11 @@ describe('ReactCompositeComponent', function() { expect(function() { innerInstance.setProps({value: 1}); }).toThrow( - 'Invariant Violation: replaceProps(...): You called `setProps` or ' + - '`replaceProps` on a component with a parent. This is an anti-pattern ' + - 'since props will get reactively updated when rendered. Instead, ' + - 'change the owner\'s `render` method to pass the correct value as ' + - 'props to the component where it is created.' + 'Invariant Violation: setProps(...): You called `setProps` on a ' + + 'component with a parent. This is an anti-pattern since props will get ' + + 'reactively updated when rendered. Instead, change the owner\'s ' + + '`render` method to pass the correct value as props to the component ' + + 'where it is created.' ); }); diff --git a/src/core/__tests__/ReactCompositeComponentState-test.js b/src/core/__tests__/ReactCompositeComponentState-test.js index 95d53d91e3..ffa1ac2e8a 100644 --- a/src/core/__tests__/ReactCompositeComponentState-test.js +++ b/src/core/__tests__/ReactCompositeComponentState-test.js @@ -154,7 +154,7 @@ describe('ReactCompositeComponent-state', function() { [ 'render', 'orange', null ], [ 'componentDidMount-start', 'orange', null ], // setState-sunrise and setState-orange should be called here, - // after the bug in # + // after the bug in #1740 // componentDidMount() called setState({color:'yellow'}), currently this // occurs inline. // In a future where setState() is async, this test result will change. diff --git a/src/modern/class/ReactComponentBase.js b/src/modern/class/ReactComponentBase.js index fcdc0e2b79..c09ba53ea9 100644 --- a/src/modern/class/ReactComponentBase.js +++ b/src/modern/class/ReactComponentBase.js @@ -11,7 +11,7 @@ 'use strict'; -var ReactInstanceMap = require('ReactInstanceMap'); +var ReactUpdateQueue = require('ReactUpdateQueue'); var invariant = require('invariant'); var warning = require('warning'); @@ -53,17 +53,10 @@ ReactComponentBase.prototype.setState = function(partialState, callback) { 'instead, use forceUpdate().' ); } - - var internalInstance = ReactInstanceMap.get(this); - invariant( - internalInstance, - 'setState(...): Can only update a mounted or mounting component. ' + - 'This usually means you called setState() on an unmounted ' + - 'component.' - ); - internalInstance.setState( - partialState, callback - ); + ReactUpdateQueue.enqueueSetState(this, partialState); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } }; /** @@ -81,14 +74,10 @@ ReactComponentBase.prototype.setState = function(partialState, callback) { * @protected */ ReactComponentBase.prototype.forceUpdate = function(callback) { - var internalInstance = ReactInstanceMap.get(this); - invariant( - internalInstance, - 'forceUpdate(...): Can only force an update on mounted or mounting ' + - 'components. This usually means you called forceUpdate() on an ' + - 'unmounted component.' - ); - internalInstance.forceUpdate(callback); + ReactUpdateQueue.enqueueForceUpdate(this); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } }; /**