简体   繁体   中英

Why is state being updated to latest message if I use the spread operator?

I have a react application in which there is a form with some fields along with validation. However, the validations are not working as I expect them to work. The code is below:

state = {
  user: null,
  errors: {}
}

onSubmitHandler = e => {
  e.preventDefault();
  //Check if first Name is returned empty
  if (this.state.user.name.trim().length === 0) {
    this.setState({
      errors: {
        ...this.state.errors,
        name: "This field is required"
      }
    });
  }
  //Check if last name is returned empty
  if (this.state.user.surname.trim().length === 0) {
    this.setState({
      errors: {
        ...this.state.errors,
        surname: "This field is required"
      }
    });
    return;
  }
}

Now when I do not provide both the input fields and leave them blank, I expect my state to be showing both error messages ie errors: {name: "This field is required", surname: "This field is required"}

But I am only getting the recent error that is for a surname. So my result shows like errors: {surname: "This field is required"}

Why is it overriding the first error message? Any help would be appreciated. The code for input field is not shown as they are just input tags with onChange event handler.

That is because the second spread still uses the old value of this.state.errors as it has not changed yet. Both setStates may be (in your case : definately were) doing the state updates later, after the onSubmitHandler has executed.

Read also https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Change the setState arguments to functions which use the latest state automatically:

onSubmitHandler = e => {
  e.preventDefault();
  //Check if first Name is returned empty
  if (this.state.user.name.trim().length === 0) {
    this.setState(state => ({
      errors: {
        ...state.errors,
        name: "This field is required"
      }
    }));
  }
  //Check if last name is returned empty
  if (this.state.user.surname.trim().length === 0) {
    this.setState(state => ({
      errors: {
        ...state.errors,
        surname: "This field is required"
      }
    }));
    return;
  }
}

Two this.setState calls in the onSubmitHandler will be batched together.

From the React Docs - setState():

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

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

So when you do ...this.state.errors , this.state.errors in both if blocks, refers to an empty object. As a result, you only see surname key in the errors object in the state.

You can fix this problem by copying the this.state.errors object, add keys in the copied object and then pass that copied object to this.setState .

onSubmitHandler = e => {
    e.preventDefault();
    
    // clone the "errors" object
    const errors = { ...this.state.errors };
    
    //Check if first Name is returned empty
    if(this.state.user.name.trim().length === 0) {
         errors.name = "This field is required";
    }
            
    //Check if last name is returned empty
    if(this.state.user.surname.trim().length === 0) {
        errors.surname = "This field is required";
    }

    this.setState({ errors });
}

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