Skip to main content

React and setState method

· 3 min read
Matej Jelluš
Tech leader and IT nerd who is constantly trying new things, sharing his experiences and still enjoys writing code in his free time. Currently looking for new challenges and opportunities.

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.