简体   繁体   中英

ReactJs dynamic setState not working in for loop

I got a obj in state and all by default are false :

obj: {
  field1: false,
  field2: false,
  field3: false
}

I want to set all to true after submit , all are dynamic:

handleSubmit = (e) => {
  e.preventDefault();

  const doValid = {
    field1: true,
    field2: true,
    field3: true,
  }

//this is static, in real code it comes from validator and decide to be false or true

  for (let i = 0; i < Object.keys(doValid).length; i++) {
    this.handleValid(Object.keys(doValid)[i], Object.values(doValid)[i]);
  }
}

And here is I set each state to true dynamically:

handleValid = (type, v) => {

  this.setState({
    ...this.state,
    obj: { ...this.state.obj,
      [type]: [v]
    }
      })

}

As you see, I used [type] and [v] for this, but the problems :

  1. When I click on submit button, state not change to true (I know maybe it's a delay to get changed states) (if I right ignore this one)
  2. If I click again, now I see just last item changed to true , but here is two problems:

a: Why just last item changed? b: Why it changed like this? with bracket, that make it to array but it just a dynamic to decide it's true or false:

field1: false
field2: false
field3: [true]

It should be like this:

field1: true
field2: true
field3: true

JSFiddle

(See console log please in jsfiddle)

You just need to do like:

this.setState({
  ...this.state,
  obj: {
   ...this.state.obj,
   [type]:v
  }
})

BTW, I would be doing simply like instead of for loop:

this.setState({
  ...this.state,
  obj: {
   ...this.state.obj,
   ...doValid
  }
})

FYI, Inside the loop, this.state is returning the previous value, as the pending state update has not been applied yet. See this post for more information.

Here's a working fiddle for you:

https://jsfiddle.net/09x2g5L6/

在此处输入图像描述

It looks like you wrote a lot of complicated code just to do:

this.setState({
  ...this.state,
  obj: { ...this.state.obj, ...doValid },
});

You seem to have trouble understanding scope, here is a good book on that and I'd advice you read the whole series as it's a great series.

Here is a working example of my code:

 class App extends React.Component { state = { obj: { field1: 2, field2: 3, field3: 4, }, }; clickHandler = () => { const doValid = { field1: true, field2: true, field3: true, }; this.setState({...this.state, obj: {...this.state.obj, ...doValid }, }); }; render() { return ( <div> <button onClick={this.clickHandler}> click me </button> <pre> {JSON.stringify(this.state, undefined, 2)} </pre> </div> ); } } ReactDOM.render(<App />, document.getElementById('root'));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

When determinig new values of state based on the previous values, we pass a function to setState to refer to previous state values.

setState() also accepts a function. This function accepts the previous state and current props of the component which it uses to calculate and return the next state.

  handleValid = (type, v) => {
    this.setState(prevState => ({
      ...prevState,
      obj: { ...prevState.obj, [type]: v },
    }));
  };

Why pass a function to setState? The thing is, state updates may be asynchronous.

Think about what happens when setState() is called. React will first merge the object you passed to setState() into the current state. Then it will start that reconciliation thing. It will create a new React Element tree (an object representation of your UI), diff the new tree against the old tree, figure out what has changed based on the object you passed to setState(), then finally update the DOM

Please follow this beautiful explanation of functional setState and what happens when we call setState() link

First of all javascript code runs asynchronously and setState return promise which will execute in later time not instantly so one solution you can do is create a delay OR you can change your handleValid as:

handleValid = async (type, v) => {

 await this.setState({
     ...this.state,
     obj: { ...this.state.obj,
     [type]: [v]
   }
  })

}

You need to change [v] to v. When you use brackets, it thinks it as an array and makes it an array.

You are trying to change state simultaneously and when you are changing it, you are copying the old state, because it didn't change yet. setState doesn't happen simulaneously.

You need to remove handleValid function and change handleSubmit with below code.

handleSubmit = (e) => {
            e.preventDefault();
            console.log(this.state.obj);

                    const doValid = {
                        field1: true,
                        field2: true,
                        field3: true,
                    }
        //you need to copy the state and change its values. You setState after you are done with it.

      let newState = {...this.state};
      for (let i = 0; i < Object.keys(doValid).length; i++) {
          newState.obj[Object.keys(doValid)[i]] = Object.values(doValid)[i];
      }
      //now we made our new state and will update it.
      this.setState(newState, () => {
       //after change successfully happened, this code will fire.
       console.log(this.state.obj);
      })
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM