diff --git a/src/classic/class/ReactClass.js b/src/classic/class/ReactClass.js
index a206bd2038eff..62c668712a2b0 100644
--- a/src/classic/class/ReactClass.js
+++ b/src/classic/class/ReactClass.js
@@ -742,7 +742,8 @@ var ReactClassMixin = {
var internalInstance = ReactInstanceMap.get(this);
invariant(
internalInstance,
- 'setProps(...): Can only update a mounted component.'
+ 'setProps(...): Can only update a mounted or mounting component. ' +
+ 'This usually means you called setProps() on an unmounted component.'
);
internalInstance.setProps(
partialProps,
@@ -797,6 +798,30 @@ var ReactClass = {
if (this.__reactAutoBindMap) {
bindAutoBindMethods(this);
}
+
+ this.props = props;
+ this.state = null;
+
+ // ReactClasses doesn't have constructors. Instead, they use the
+ // getInitialState and componentWillMount methods for initialization.
+
+ var initialState = this.getInitialState ? this.getInitialState() : null;
+ if (__DEV__) {
+ // We allow auto-mocks to proceed as if they're returning null.
+ if (typeof initialState === 'undefined' &&
+ this.getInitialState._isMockFunction) {
+ // This is probably bad practice. Consider warning here and
+ // deprecating this convenience.
+ initialState = null;
+ }
+ }
+ invariant(
+ typeof initialState === 'object' && !Array.isArray(initialState),
+ '%s.getInitialState(): must return an object or null',
+ Constructor.displayName || 'ReactCompositeComponent'
+ );
+
+ this.state = initialState;
};
Constructor.prototype = new ReactClassBase();
Constructor.prototype.constructor = Constructor;
@@ -823,9 +848,6 @@ var ReactClass = {
if (Constructor.prototype.getInitialState) {
Constructor.prototype.getInitialState.isReactClassApproved = {};
}
- if (Constructor.prototype.componentWillMount) {
- Constructor.prototype.componentWillMount.isReactClassApproved = {};
- }
}
invariant(
diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js
index 41215f6cb1475..9b3f94b8cd2d3 100644
--- a/src/core/ReactCompositeComponent.js
+++ b/src/core/ReactCompositeComponent.js
@@ -40,20 +40,6 @@ function getDeclarationErrorAddendum(component) {
return '';
}
-function validateLifeCycleOnReplaceState(instance) {
- var compositeLifeCycleState = instance._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 update while unmounting component. This ' +
- 'usually means you called setState() on an unmounted component.'
- );
-}
-
/**
* `ReactCompositeComponent` maintains an auxiliary life cycle state in
* `this._compositeLifeCycleState` (which can be null).
@@ -123,7 +109,8 @@ var ReactCompositeComponentMixin = assign({},
this._rootNodeID = null;
this._instance.props = element.props;
- this._instance.state = null;
+ // instance.state get set up to its proper initial value in mount
+ // which may be null.
this._instance.context = null;
this._instance.refs = emptyObject;
@@ -190,15 +177,7 @@ var ReactCompositeComponentMixin = assign({},
}
inst.props = this._processProps(this._currentElement.props);
- var initialState = inst.getInitialState ? inst.getInitialState() : null;
if (__DEV__) {
- // We allow auto-mocks to proceed as if they're returning null.
- if (typeof initialState === 'undefined' &&
- inst.getInitialState._isMockFunction) {
- // This is probably bad practice. Consider warning here and
- // deprecating this convenience.
- initialState = null;
- }
// Since plain JS classes are defined without any special initialization
// logic, we can not catch common errors early. Therefore, we have to
// catch them here, at initialization time, instead.
@@ -210,14 +189,6 @@ var ReactCompositeComponentMixin = assign({},
'Did you mean to define a state property instead?',
this.getName() || 'a component'
);
- warning(
- !inst.componentWillMount ||
- inst.componentWillMount.isReactClassApproved,
- 'componentWillMount was defined on %s, a plain JavaScript class. ' +
- 'This is only supported for classes created using React.createClass. ' +
- 'Did you mean to define a constructor instead?',
- this.getName() || 'a component'
- );
warning(
!inst.propTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
@@ -239,9 +210,14 @@ var ReactCompositeComponentMixin = assign({},
(this.getName() || 'A component')
);
}
+
+ var initialState = inst.state;
+ if (initialState === undefined) {
+ inst.state = initialState = null;
+ }
invariant(
typeof initialState === 'object' && !Array.isArray(initialState),
- '%s.getInitialState(): must return an object or null',
+ '%s.state: must be set to an object or null',
this.getName() || 'ReactCompositeComponent'
);
inst.state = initialState;
@@ -396,11 +372,32 @@ var ReactCompositeComponentMixin = assign({},
* @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.replaceState(
- assign({}, this._pendingState || this._instance.state, partialState),
- callback
+ 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);
+ }
},
/**
@@ -416,7 +413,18 @@ var ReactCompositeComponentMixin = assign({},
* @protected
*/
replaceState: function(completeState, callback) {
- validateLifeCycleOnReplaceState(this);
+ 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
@@ -1004,22 +1012,15 @@ var ShallowMixin = assign({},
// No context for shallow-mounted components.
inst.props = this._processProps(this._currentElement.props);
- var initialState = inst.getInitialState ? inst.getInitialState() : null;
- if (__DEV__) {
- // We allow auto-mocks to proceed as if they're returning null.
- if (typeof initialState === 'undefined' &&
- inst.getInitialState._isMockFunction) {
- // This is probably bad practice. Consider warning here and
- // deprecating this convenience.
- initialState = null;
- }
+ var initialState = inst.state;
+ if (initialState === undefined) {
+ inst.state = initialState = null;
}
invariant(
typeof initialState === 'object' && !Array.isArray(initialState),
- '%s.getInitialState(): must return an object or null',
+ '%s.state: must be set to an object or null',
this.getName() || 'ReactCompositeComponent'
);
- inst.state = initialState;
this._pendingState = null;
this._pendingForceUpdate = false;
diff --git a/src/core/__tests__/ReactComponentLifeCycle-test.js b/src/core/__tests__/ReactComponentLifeCycle-test.js
index 8c108f1c806e1..1c69f7ebacff9 100644
--- a/src/core/__tests__/ReactComponentLifeCycle-test.js
+++ b/src/core/__tests__/ReactComponentLifeCycle-test.js
@@ -104,7 +104,11 @@ describe('ReactComponentLifeCycle', function() {
ReactInstanceMap = require('ReactInstanceMap');
getCompositeLifeCycle = function(instance) {
- return ReactInstanceMap.get(instance)._compositeLifeCycleState;
+ var internalInstance = ReactInstanceMap.get(instance);
+ if (!internalInstance) {
+ return null;
+ }
+ return internalInstance._compositeLifeCycleState;
};
getLifeCycleState = function(instance) {
@@ -221,7 +225,7 @@ describe('ReactComponentLifeCycle', function() {
}).not.toThrow();
});
- it('should allow update state inside of getInitialState', function() {
+ it('should not allow update state inside of getInitialState', function() {
var StatefulComponent = React.createClass({
getInitialState: function() {
this.setState({stateField: 'something'});
@@ -234,16 +238,15 @@ describe('ReactComponentLifeCycle', function() {
);
}
});
- var instance =