Reputation:
Scenario 1:
const { foo } = this.state;
Scenario 2:
this.setState(({ foo }) => { ... });
is foo
guaranteed to be identical between these two cases? Or will setState
ever run async and return a different value based on other setState
s?
Upvotes: 3
Views: 1066
Reputation: 4176
As the React docs mention:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
So we can use the second argument of the setState
to pass a callback where we execute our logic which depends on having the updated value of foo. However your initial question was whether the value of foo in const { foo } = this.state;
and the value of foo in this.setState(({ foo }) => { ... });
was the same.
In order check this we can compare executing a setState
followed by this.state.foo
and a setState
followed by another setState
(the second one will just log the value of foo instead of mutating it). Please refer to the following snippet:
class Example extends React.Component {
constructor() {
super();
this.state = {
foo: 0,
boo: 0
}
}
handleClickSetState = () => {
this.setState(({foo}) => ({foo: foo+1}));
this.setState(({foo}) => console.log("inside setState. foo: ", foo));
}
handleClickState = () => {
this.setState(({boo}) => ({boo: boo+1}));
console.log("outside setState. boo: ", this.state.boo)
}
render() {
return <React.Fragment>
<button onClick={this.handleClickState}>Test state</button>
<button onClick={this.handleClickSetState}>Test setState</button>
</React.Fragment>
}
}
ReactDOM.render(<Example />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As we can see the "state case" lags behind by 1, which we would expect since setState
is async (and we didn't use the second argument of setState
). However the "setState case" shows that the correct value is always shown, even if we don't utilize the second argument callback in setState.
In conclusion const { foo } = this.state;
will always give you the immediate value of foo, regardless of pending state merges, while this.setState(({ foo }) => { ... });
seems like it will first finish pending updates before executing the callback, which means foo will always have the most recent value.
Upvotes: 2
Reputation: 55750
State might be state when you access the state immediately after using this.setState
, as this.setState
is async
.
If you need to compute on basis on the updated state i.e ( after this.state
is invoked ), you can use the 2nd argument for the method which is a callback that gets triggered after the state changes are committed.
this.setState(updateState, () => {
const { foo } = this.state;
console.log(foo); // updated foo
});
function updatedState({ foo }) {
// you can be sure that `foo`
// is from the previous state
// before setState is called
}
Upvotes: 2
Reputation: 7767
setState
is async, and pulling values off state to use in setState
is a potential source of bugs. setState
can take multiple arguments. It can take just a new state, a callback that takes old state + props and returns new state, new state and a function to run after setting new state, or a combination. Example:
this.setState(
// function taking previous state + props,
// which should return updated state.
// this could also just be an object with
// the new state, if you don't need to rely
// on previous state.
(previousState, props) => {
if (previousState.something) {
return { something: somethingElse }
} else {
return { something: anotherThing }
}
}, () => {
// safe to use new state here,
// since state has been updated.
// but it's recommended to use
// componentDidUpdate instead!
})
Upvotes: 1