React setState not Updating Immediately

ReactjsReduxStateSetstate

Reactjs Problem Overview


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?

Reactjs Solutions


Solution 1 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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 - Reactjs

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionlost9123193View Question on Stackoverflow
Solution 1 - ReactjsBen HareView Answer on Stackoverflow
Solution 2 - ReactjsrossipediaView Answer on Stackoverflow
Solution 3 - ReactjsGBLView Answer on Stackoverflow
Solution 4 - ReactjsTej SanuraView Answer on Stackoverflow
Solution 5 - ReactjsAlirezaView Answer on Stackoverflow
Solution 6 - ReactjsKumaresanView Answer on Stackoverflow
Solution 7 - Reactjslost9123193View Answer on Stackoverflow
Solution 8 - ReactjsCheapSteaksView Answer on Stackoverflow
Solution 9 - ReactjsAnish M PrasadView Answer on Stackoverflow
Solution 10 - ReactjssudonitinView Answer on Stackoverflow
Solution 11 - ReactjsMelkam MekonnenView Answer on Stackoverflow
Solution 12 - ReactjsBalram SinghView Answer on Stackoverflow