Reputation: 15665
In my increaseCount method I have 3 different ways of increasing the count. I thought the first method could be used twice but it doesn't work as it seems to merge the setState in the callback. What's the proper way to use a callback function and why does the arrow notation work? How is prevState.count being defined? It is never set to 0
import React from "react";
import { render } from "react-dom";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increaseCount() {
console.log(this);
this.setState({ count: this.state.count }, function() {
this.setState({ count: this.state.count + 1 });
});
this.setState({ count: this.state.count + 1 });
this.setState(prevState=>({ count: prevState.count + 1 }));
}
render() {
return (
<div style={styles}>
<div>
<button onClick={() => this.increaseCount()}>Increase</button>
</div>
<h2>{this.state.count}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Upvotes: 0
Views: 536
Reputation: 2865
increaseCount
function need to be an arrow function, since you didn't bind it.setState
is an async function, it will not execute immoderately after called. So the 2nd setState
won't execute correctly. In general, it is better to setState
once on a single event. Link to docthis.setState(function() {})
is valid, but I haven't seen this.setState({ count: this.state.count }, function() {})
in anywhere in the doc.To get expected result based on your code, this is the correct pattern:
increaseCount = () => {
let countBuffer = this.state.count;
countBuffer++;
countBuffer++;
countBuffer++;
this.setState(prevState => ({ count: countBuffer }));
}
This should work, but it's not the preferred method, due to it requires multiple times of unnecessary rendering:
increaseCount = async () => {
await this.setState(function(){ return { count: this.state.count + 1 } });
await this.setState({ count: this.state.count + 1 });
this.setState(prevState=>({ count: prevState.count + 1 }));
}
Upvotes: 1
Reputation: 32146
As far as I know, only your third method is a proper way to increment a state value. Going through your attempts:
this.setState({ count: this.state.count }, function() {
this.setState({ count: this.state.count + 1 });
});
This snippet is redundant (you set the count to the same value), then when that state change has finished, you then increment the count by adding 1 to this.state.count
. While that's fine here, you could have just done that to begin with (as in your next snippet).
Next:
this.setState({ count: this.state.count + 1 });
Better, but the setState
method is asynchronous, so the actual state change won't occur until later. That means that this will work in most cases, but if you were to call it twice it would not increment by two, since this.state.count
hasn't updated yet when the second call occurs.
Finally, your last attempt looks perfect:
this.setState(prevState=>({ count: prevState.count + 1 }));
This uses the functional setState
syntax where react will give your callback function the current state, and you are expected to read it and return your desired new state. Using this style, you could call it twice (or as many as you want) times, and each call would increment the count properly.
To address some of your other questions:
it seems to merge the setState in the callback.
Correct. From the documentation linked below:
React may batch multiple setState() calls into a single update for performance.
What's the proper way to use a callback function and why does the arrow notation work?
The functional setState
is a new-ish react feature. Arrow functions are nice but not required (unless you access this
inside the callback). From their documentation here:
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
We used an arrow function above, but it also works with regular functions:
// Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
How is prevState.count being defined? It is never set to 0
It is being set to zero in the constructor. The line:
this.state = {
count: 0
};
is what sets it initially. From that point, the current state (whatever it may be) is passed your setState
callback function.
Sorry for the wall, this answer got out of hand before I knew it. Hopefully that helps, LMK if I missed the point or there are more questions
Upvotes: 4
Reputation: 625
you should bind your callback function in order to use 'this'
your constructor should look like this:
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increaseCount = this.increaseCount.bind(this)
}
Edit:
and your inreaseCount function should look like:
increaseCount() {
console.log(this);
this.setState(prevState=> ({ count: prevState.count + 1 })) ;
}
Upvotes: 0