From ea932ddf7b871bb0d5b1079f40a983c9eb44f5fb Mon Sep 17 00:00:00 2001 From: Nelo Mitranim Date: Sun, 22 Jul 2018 16:22:21 +0300 Subject: [PATCH] `setState` now creates new versions instead of mutating state --- src/component.js | 10 ++++++---- test/browser/lifecycle.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/component.js b/src/component.js index 3c2e442fa0..e3683472bf 100644 --- a/src/component.js +++ b/src/component.js @@ -46,14 +46,16 @@ extend(Component.prototype, { /** * Update component state and schedule a re-render. - * @param {object} state A hash of state properties to update with new values + * @param {object} state A dict of state properties to be shallowly merged + * into the current state, or a function that will produce such a dict. The + * function is called with the current state and props. * @param {() => void} callback A function to be called once component state is * updated */ setState(state, callback) { - let s = this.state; - if (!this.prevState) this.prevState = extend({}, s); - extend(s, typeof state==='function' ? state(s, this.props) : state); + const prev = this.prevState = this.state; + if (typeof state === 'function') state = state(prev, this.props); + this.state = extend(extend({}, prev), state); if (callback) this._renderCallbacks.push(callback); enqueueRender(this); }, diff --git a/test/browser/lifecycle.js b/test/browser/lifecycle.js index d6b5699363..8c8b1db16c 100644 --- a/test/browser/lifecycle.js +++ b/test/browser/lifecycle.js @@ -1186,7 +1186,7 @@ describe('Lifecycle methods', () => { }); - describe('shouldComponentUpdate', () => { + describe('#shouldComponentUpdate', () => { let setState; class Should extends Component { @@ -1325,6 +1325,38 @@ describe('Lifecycle methods', () => { }); + describe('#setState', () => { + it('should not mutate state, only create new versions', () => { + const stateConstant = {}; + let didMount = false; + let componentState; + + expect(stateConstant).to.deep.equal({}); + + class Stateful extends Component { + constructor() { + super(...arguments); + this.state = stateConstant; + } + + componentDidMount() { + didMount = true; + this.setState({key: 'value'}, () => { + componentState = this.state; + }); + } + } + + render(, scratch); + rerender(); + + expect(didMount).to.equal(true); + expect(componentState).to.deep.equal({key: 'value'}); + expect(stateConstant).to.deep.equal({}); + }); + }), + + describe('Lifecycle DOM Timing', () => { it('should be invoked when dom does (DidMount, WillUnmount) or does not (WillMount, DidUnmount) exist', () => { let setState;