diff --git a/docs/docs/ref-02-component-api.md b/docs/docs/ref-02-component-api.md index 2deeef703ed27..1583adf3eeb6a 100644 --- a/docs/docs/ref-02-component-api.md +++ b/docs/docs/ref-02-component-api.md @@ -36,7 +36,7 @@ Merges nextState with the current state. This is the primary method you use to t replaceState(object nextState[, function callback]) ``` -Like `setState()` but deletes any pre-existing state keys that are not in nextState. +Like `setState()` but deletes any pre-existing state keys that are not in nextState. If you pass `null` instead of object, `this.state` will be reset back to `null`. ### forceUpdate() diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index c3d880ce7135d..febd41c1df0fb 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -130,6 +130,7 @@ var ReactCompositeComponentMixin = assign({}, this._pendingContext = null; this._pendingState = null; this._pendingForceUpdate = false; + this._removeState = false; this._compositeLifeCycleState = null; this._renderedComponent = null; @@ -208,6 +209,7 @@ var ReactCompositeComponentMixin = assign({}, this._pendingState = null; this._pendingForceUpdate = false; + this._removeState = false; if (inst.componentWillMount) { inst.componentWillMount(); @@ -261,6 +263,7 @@ var ReactCompositeComponentMixin = assign({}, this._pendingForceUpdate = false; this._pendingCallbacks = null; this._pendingElement = null; + this._removeState = false; ReactComponent.Mixin.unmountComponent.call(this); @@ -378,6 +381,11 @@ var ReactCompositeComponentMixin = assign({}, replaceState: function(completeState, callback) { validateLifeCycleOnReplaceState(this); this._pendingState = completeState; + + if (completeState === null) { + this._removeState = true; + } + 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 @@ -563,6 +571,7 @@ var ReactCompositeComponentMixin = assign({}, if (this._pendingElement == null && this._pendingState == null && this._pendingContext == null && + !this._removeState && !this._pendingForceUpdate) { return; } @@ -678,7 +687,13 @@ var ReactCompositeComponentMixin = assign({}, this._compositeLifeCycleState = null; - var nextState = this._pendingState || inst.state; + var nextState; + if (this._removeState) { + nextState = null; + this._removeState = false; + } else { + nextState = this._pendingState || inst.state; + } this._pendingState = null; var shouldUpdate = diff --git a/src/core/__tests__/ReactCompositeComponentState-test.js b/src/core/__tests__/ReactCompositeComponentState-test.js index 10fa09a1bf929..9caf1371ee396 100644 --- a/src/core/__tests__/ReactCompositeComponentState-test.js +++ b/src/core/__tests__/ReactCompositeComponentState-test.js @@ -30,7 +30,7 @@ describe('ReactCompositeComponent-state', function() { TestComponent = React.createClass({ peekAtState: function(from, state) { - if (state) { + if (state !== undefined) { this.props.stateListener(from, state && state.color); } else { var internalInstance = ReactInstanceMap.get(this); @@ -55,7 +55,7 @@ describe('ReactCompositeComponent-state', function() { render: function() { this.peekAtState('render'); - return
{this.state.color}
; + return
{this.state && this.state.color}
; }, componentWillMount: function() { @@ -113,9 +113,9 @@ describe('ReactCompositeComponent-state', function() { instance.setProps({nextColor: 'green'}); instance.setFavoriteColor('blue'); instance.forceUpdate(); + instance.replaceState(null); React.unmountComponentAtNode(container); - expect(stateListener.mock.calls).toEqual([ // there is no state when getInitialState() is called [ 'getInitialState', null, null ], @@ -162,9 +162,17 @@ describe('ReactCompositeComponent-state', function() { [ 'render', 'blue', null ], [ 'componentDidUpdate-currentState', 'blue', null ], [ 'componentDidUpdate-prevState', 'blue' ], + // replaceState(null) + [ 'shouldComponentUpdate-currentState', 'blue', null ], + [ 'shouldComponentUpdate-nextState', null ], + [ 'componentWillUpdate-currentState', 'blue', null ], + [ 'componentWillUpdate-nextState', null ], + [ 'render', null, null ], + [ 'componentDidUpdate-currentState', null, null ], + [ 'componentDidUpdate-prevState', 'blue' ], // unmountComponent() // state is available within `componentWillUnmount()` - [ 'componentWillUnmount', 'blue', null ] + [ 'componentWillUnmount', null, null ] ]); }); });