diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index 160aa9d7dd..73ebdab706 100644 --- a/src/isomorphic/classic/class/ReactClass.js +++ b/src/isomorphic/classic/class/ReactClass.js @@ -16,9 +16,10 @@ var ReactElement = require('ReactElement'); var ReactErrorUtils = require('ReactErrorUtils'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); -var ReactUpdateQueue = require('ReactUpdateQueue'); +var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); var assign = require('Object.assign'); +var emptyObject = require('emptyObject'); var invariant = require('invariant'); var keyMirror = require('keyMirror'); var keyOf = require('keyOf'); @@ -712,9 +713,9 @@ var ReactClassMixin = { * type signature and the only use case for this, is to avoid that. */ replaceState: function(newState, callback) { - ReactUpdateQueue.enqueueReplaceState(this, newState); + this.updater.enqueueReplaceState(this, newState); if (callback) { - ReactUpdateQueue.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback); } }, @@ -725,7 +726,7 @@ var ReactClassMixin = { * @final */ isMounted: function() { - return ReactUpdateQueue.isMounted(this); + return this.updater.isMounted(this); }, /** @@ -738,9 +739,9 @@ var ReactClassMixin = { * @deprecated */ setProps: function(partialProps, callback) { - ReactUpdateQueue.enqueueSetProps(this, partialProps); + this.updater.enqueueSetProps(this, partialProps); if (callback) { - ReactUpdateQueue.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback); } }, @@ -754,9 +755,9 @@ var ReactClassMixin = { * @deprecated */ replaceProps: function(newProps, callback) { - ReactUpdateQueue.enqueueReplaceProps(this, newProps); + this.updater.enqueueReplaceProps(this, newProps); if (callback) { - ReactUpdateQueue.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback); } }, }; @@ -783,7 +784,7 @@ var ReactClass = { * @public */ createClass: function(spec) { - var Constructor = function(props, context) { + var Constructor = function(props, context, updater) { // This constructor is overridden by mocks. The argument is used // by mocks to assert on what gets mounted. @@ -802,6 +803,9 @@ var ReactClass = { this.props = props; this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + this.state = null; // ReactClasses doesn't have constructors. Instead, they use the diff --git a/src/isomorphic/modern/class/ReactComponent.js b/src/isomorphic/modern/class/ReactComponent.js index cb099b8961..ddf7bc14ef 100644 --- a/src/isomorphic/modern/class/ReactComponent.js +++ b/src/isomorphic/modern/class/ReactComponent.js @@ -11,17 +11,22 @@ 'use strict'; -var ReactUpdateQueue = require('ReactUpdateQueue'); +var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); +var emptyObject = require('emptyObject'); var invariant = require('invariant'); var warning = require('warning'); /** * Base class helpers for the updating state of a component. */ -function ReactComponent(props, context) { +function ReactComponent(props, context, updater) { this.props = props; this.context = context; + this.refs = emptyObject; + // We initialize the default updater but the real one gets injected by the + // renderer. + this.updater = updater || ReactNoopUpdateQueue; } /** @@ -64,9 +69,9 @@ ReactComponent.prototype.setState = function(partialState, callback) { 'instead, use forceUpdate().' ); } - ReactUpdateQueue.enqueueSetState(this, partialState); + this.updater.enqueueSetState(this, partialState); if (callback) { - ReactUpdateQueue.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback); } }; @@ -85,9 +90,9 @@ ReactComponent.prototype.setState = function(partialState, callback) { * @protected */ ReactComponent.prototype.forceUpdate = function(callback) { - ReactUpdateQueue.enqueueForceUpdate(this); + this.updater.enqueueForceUpdate(this); if (callback) { - ReactUpdateQueue.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback); } }; diff --git a/src/isomorphic/modern/class/ReactNoopUpdateQueue.js b/src/isomorphic/modern/class/ReactNoopUpdateQueue.js new file mode 100644 index 0000000000..69fc3ddb1b --- /dev/null +++ b/src/isomorphic/modern/class/ReactNoopUpdateQueue.js @@ -0,0 +1,126 @@ +/** + * 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 ReactNoopUpdateQueue + */ + +'use strict'; + +var warning = require('warning'); + +function warnTDZ(publicInstance, callerName) { + if (__DEV__) { + warning( + false, + '%s(...): Can only update a mounted or mounting component. ' + + 'This usually means you called %s() on an unmounted component. ' + + 'This is a no-op. Please check the code for the %s component.', + callerName, + callerName, + publicInstance.constructor && publicInstance.constructor.displayName || '' + ); + } +} + +/** + * This is the abstract API for an update queue. + */ +var ReactNoopUpdateQueue = { + + /** + * Checks whether or not this composite component is mounted. + * @param {ReactClass} publicInstance The instance we want to test. + * @return {boolean} True if mounted, false otherwise. + * @protected + * @final + */ + isMounted: function(publicInstance) { + return false; + }, + + /** + * 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) { }, + + /** + * 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 `shouldComponentUpdate`, but it will invoke + * `componentWillUpdate` and `componentDidUpdate`. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @internal + */ + enqueueForceUpdate: function(publicInstance) { + warnTDZ(publicInstance, 'forceUpdate'); + }, + + /** + * 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) { + warnTDZ(publicInstance, 'replaceState'); + }, + + /** + * 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) { + warnTDZ(publicInstance, 'setState'); + }, + + /** + * 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) { + warnTDZ(publicInstance, 'setProps'); + }, + + /** + * Replaces all of the props. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} props New props. + * @internal + */ + enqueueReplaceProps: function(publicInstance, props) { + warnTDZ(publicInstance, 'replaceProps'); + }, + +}; + +module.exports = ReactNoopUpdateQueue; diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index c95a58593d..373568b39d 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -22,6 +22,7 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactReconciler = require('ReactReconciler'); +var ReactUpdateQueue = require('ReactUpdateQueue'); var assign = require('Object.assign'); var emptyObject = require('emptyObject'); @@ -130,7 +131,7 @@ var ReactCompositeComponentMixin = { ); // Initialize the public class - var inst = new Component(publicProps, publicContext); + var inst = new Component(publicProps, publicContext, ReactUpdateQueue); if (__DEV__) { // This will throw later in _renderValidatedComponent, but add an early @@ -150,6 +151,7 @@ var ReactCompositeComponentMixin = { inst.props = publicProps; inst.context = publicContext; inst.refs = emptyObject; + inst.updater = ReactUpdateQueue; this._instance = inst;