React and setState method
The first thing you read about setState
- it is asynchronous. I understand it but still did not realize a couple of things.
The problem
Of course I know that if you set some state variable and try to read it immediately, it won't be changed :
// this.state.value is 0
this.setState({value: 1});
// this.state.value is till 0
But tricky question would be, what it does if you increment that state 3 times?
// this.state.value is 0
this.setState({value: this.state.value + 1});
this.setState({value: this.state.value + 1});
this.setState({value: this.state.value + 1});
I asked it a couple of colleagues and maybe some of them did now ( or lucky guess ) the right answer, but didn't know why, or didn't know it. And I would say, this is not that tricky. Because this.state.value
equals 0 you actually set it to 1 each time.
this.setState({value: 0 + 1});
this.setState({value: 0 + 1});
this.setState({value: 0 + 1});
Or maybe, you have another case. What if you count the result value from some props? Let's say, we have some parent class:
// App.js
import React, { Component } from 'react';
import IncrementButton from './IncrementButton';
class App extends Component {
state = {
value: 0,
};
onIncrementButtonClick = () => {
this.setState({value: this.state.value + 1});
}
render() {
return (
<IncrementButton onClick={this.onIncrementButtonClick} value={this.state.value} />
);
}
}
export default App;
And IncrementButton
looks like this:
// IncrementButton.js
import React, { Component } from 'react';
class IncrementButton extends Component {
state = {
value: 0,
};
onButtonClick() {
this.props.onClick();
this.setState({value: this.props.value + 1});
}
render() {
return (
<button onClick={this.onButtonClick.bind(this)}>{this.state.value}</button>
);
}
}
export default IncrementButton;
If you click on the button, you call the parent method onIncrementButtonClick
which will update the parent's state, i. e. some time later, value
will be 1. Then you update the state of the IncrementButton
. You take the value from the props and increment it. The problem is, that in the props, you have still the old value, therefore you update the state to 0 + 1
. The button after first click has value 1, but in fact, it should be 2.
The solution
The solution for cases like this - when you calc / derive the next state from the actual state or from the props - is simple: use setState
but pass a function:
this.setState((prevState, props) => ({
// ...
}));
Now, let's try it with the first example:
// this.state.value = 0
this.setState((prevState) => ({value: prevState.value + 1}));
this.setState((prevState) => ({value: prevState.value + 1}));
this.setState((prevState) => ({value: prevState.value + 1}));
// will be 3 after update
The same applies on the second example. Modify the onButtonClick
method of the IncrementButton
:
onButtonClick() {
this.props.onClick();
this.setState((prevState, props) => { return {value: props.value + 1} });
}
And now it works correctly! Of course you should update also onIncrementButtonClick
in the App
class.
Do you like this post? Is it helpful? I am always learning and trying new technologies, processes and approaches. When I struggle with something and finally manage to solve it, I share my experience. If you want to support me, please use button below. If you have any questions or comments, please reach me via email juffalow@juffalow.com.
I am also available as a mentor if you need help with your architecture, engineering team or if you are looking for an experienced person to validate your thoughts.