128

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?

lost9123193
  • 10,460
  • 26
  • 73
  • 113
  • Re-rendering is asynchronous to the `setState`. You may use the second `setState` argument that is invoked after the component is re-rendered. PS: it looks suspicious that you need to pass the complete component state upwards. – zerkms Jul 25 '16 at 00:29
  • @zerkms I think I understand what you mean but could you elaborate on how I would call another setState in the context of a checkbox? – lost9123193 Jul 25 '16 at 00:36
  • You don't call another `setState`. `setState` accepts a second argument that is a callback https://facebook.github.io/react/docs/component-api.html#setstate – zerkms Jul 25 '16 at 00:37
  • Does this answer your question? [Why does calling react setState method not mutate the state immediately?](https://stackoverflow.com/questions/30782948/why-does-calling-react-setstate-method-not-mutate-the-state-immediately) – ggorlen Jul 21 '22 at 17:10

13 Answers13

134

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.

Ben Hare
  • 4,365
  • 5
  • 27
  • 44
  • 10
    In arrow functions, `this` is bound to the enclosing scope, so I'm pretty sure you have that backwards. – rossipedia Oct 27 '17 at 16:09
  • 21
    The solution is not descriptive enough. – J.Ko Dec 14 '18 at 17:55
  • 3
    For functional components, at least on react 18, this does not work: `Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().` – Bersan Sep 28 '22 at 17:41
  • What if I have a mapped state like const myFunction=()=>{ Urls.map(url=>{ setImagesUrl(prev=>[...prev,url]) }) console.log(imagesUrl) // I can't read it here } . How am I gonna use my imagesUrl in the same function? – Utku AKTAS Oct 13 '22 at 13:26
35

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);
   });
}
rossipedia
  • 56,800
  • 10
  • 90
  • 93
31

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 every time myArr has changed
   // perform some action every time myArr is updated
   console.log('Updated State', myArr)
}, [myArr])
isherwood
  • 58,414
  • 16
  • 114
  • 157
Tej Sanura
  • 311
  • 4
  • 6
14

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)
    });
}
GBL
  • 386
  • 4
  • 13
13

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
}));
Alireza
  • 100,211
  • 27
  • 269
  • 172
7

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
}
clinton3141
  • 4,751
  • 3
  • 33
  • 46
Kumaresan
  • 79
  • 1
  • 3
  • 6
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – rollstuhlfahrer Feb 23 '18 at 11:47
2

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)
}
zerkms
  • 249,484
  • 69
  • 436
  • 539
lost9123193
  • 10,460
  • 26
  • 73
  • 113
2

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

CheapSteaks
  • 4,821
  • 3
  • 31
  • 48
2

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

Anish M Prasad
  • 101
  • 1
  • 7
1

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"
                }
            }));
        }
   }
sudonitin
  • 378
  • 4
  • 15
0

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.

Balram Singh
  • 1,576
  • 19
  • 30
0

Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.

  1) let errorsArray = [];
  2) let [errors, setErrors] = useState(errorsArray);
  3) let [firstName, setFirstName] = useState('');
  4) let [lastName, setLastName] = useState('');
  let [gender, setGender] = useState('');
  let [email, setEmail] = useState('');
  let [password, setPassword] = useState('');

  const performRegister = () => {
    console.log('firstName', isEmpty(firstName));
    if (isEmpty(firstName)) {
      console.log('first if statement');
      errorsArray.push({firstName: 'First Name Cannot be empty'});
    }
    if (isEmpty(lastName)) {
      errorsArray.push({lastName: 'Last Name Cannot be empty'});
    }
    if (isEmpty(gender)) {
      errorsArray.push({gender: 'Gender Cannot be empty'});
    }
    if (isEmpty(email)) {
      errorsArray.push({email: 'Email Cannot be empty'});
    }
    if (isEmpty(password)) {
      errorsArray.push({password: 'Password Cannot be empty'});
    }
    console.log('outside ERRORS array :::', errorsArray);
    setErrors(errorsArray);
    console.log('outside ERRORS :::', errors);
    if (errors.length > 0) {
      console.log('ERROR exists');
    }
  };
-1

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
          }))
        } )
Ewran
  • 328
  • 4
  • 15