Skip to content

Commit

Permalink
Merge pull request #1170 from Mitranim/master
Browse files Browse the repository at this point in the history
`setState` now creates new versions instead of mutating state
  • Loading branch information
marvinhagemeister committed Aug 2, 2018
2 parents b8a117a + 576c891 commit 5410c7e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 7 deletions.
10 changes: 6 additions & 4 deletions src/component.js
Expand Up @@ -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);
},
Expand Down
4 changes: 2 additions & 2 deletions src/vdom/component.js
Expand Up @@ -84,8 +84,8 @@ export function renderComponent(component, renderMode, mountAll, isChild) {
rendered, inst, cbase;

if (component.constructor.getDerivedStateFromProps) {
previousState = extend({}, previousState);
component.state = extend(state, component.constructor.getDerivedStateFromProps(props, state));
state = extend(extend({}, state), component.constructor.getDerivedStateFromProps(props, state));
component.state = state;
}

// if updating
Expand Down
57 changes: 56 additions & 1 deletion test/browser/lifecycle.js
Expand Up @@ -505,6 +505,31 @@ describe('Lifecycle methods', () => {
value: 3
});
});

it('should NOT mutate state, only create new versions', () => {
const stateConstant = {};
let componentState;

class Stateful extends Component {
static getDerivedStateFromProps() {
return {key: 'value'};
}

constructor() {
super(...arguments);
this.state = stateConstant;
}

componentDidMount() {
componentState = this.state;
}
}

render(<Stateful />, scratch);

expect(componentState).to.deep.equal({key: 'value'});
expect(stateConstant).to.deep.equal({});
});
});

describe("#getSnapshotBeforeUpdate", () => {
Expand Down Expand Up @@ -1186,7 +1211,7 @@ describe('Lifecycle methods', () => {
});


describe('shouldComponentUpdate', () => {
describe('#shouldComponentUpdate', () => {
let setState;

class Should extends Component {
Expand Down Expand Up @@ -1325,6 +1350,36 @@ describe('Lifecycle methods', () => {
});


describe('#setState', () => {
it('should NOT mutate state, only create new versions', () => {
const stateConstant = {};
let didMount = false;
let componentState;

class Stateful extends Component {
constructor() {
super(...arguments);
this.state = stateConstant;
}

componentDidMount() {
didMount = true;
this.setState({key: 'value'}, () => {
componentState = this.state;
});
}
}

render(<Stateful />, 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;
Expand Down

0 comments on commit 5410c7e

Please sign in to comment.