'React setState not Updating Immediately
I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
Solution 1:[1]
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Solution 2:[2]
Calling setState()
in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState()
into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState
accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
Solution 3:[3]
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState
instead of the object.
So setState((state, props) => {...})
instead of setState(object)
.
The reason is that setState
is more of a request for the state to change rather than an immediate change. React batches those setState
calls for performance improvement.
Meaning the state property you're checking might not be stable. This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
Solution 4:[4]
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called everytime when myArr has changed
// perform some action which will get fired everytime when myArr gets updated
console.log('Updated State', myArr)
}, [myArr])
Solution 5:[5]
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState()
that accepts a function rather than an object:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
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
}));
Solution 6:[6]
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
Solution 7:[7]
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Solution 8:[8]
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
Solution 9:[9]
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
Solution 10:[10]
As mentioned above setState()
is asynchronous in nature. I solved this issue simply using async
await
.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
Solution 11:[11]
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Solution 12:[12]
Here is React Hooks based solution.
Since React useState
updates state asynchronously, check them in the useEffect
hook if you need to see these changes.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow