1

I have a state with validations like this:

this.state = {
        email: '', pass: '',
        errors: {
            email: '', pass: ''
        }
    }

and when a user press a button, it validates the form like this:

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

    this.setState({errors: {pass: '', email: ''}});

    const pass = this.state.pass.trim();

    if(!isEmail(this.state.email)){
        this.setState({errors: {...this.state.errors, email: 'Invalid Email.'}});
    }

    // return; - in this way email state error will be ok

    if(pass.length < 6){
        this.setState({errors:{ ...this.state.errors, pass: 'Password Invalid'}});
    }

    return console.log(this.state);
};

I am using ... operator to append type of errors in the errors object. The problem is that, if the email is invalid and there is an error for the password the state is like this:

errors: {email: '', pass: 'Password Invalid'}

If I use return before pass check, the email error state works normal. I know that setState is async, but how to append different values of deep state object ?

gdfgdfg
  • 3,181
  • 7
  • 37
  • 83
  • 1
    Use `setState` with the callback variant? – Bergi Jun 23 '19 at 16:02
  • 1
    use `setState` with fn ? – xadm Jun 23 '19 at 16:04
  • 1
    I discourage you to use this kind of validation for real applications, what if email does not have an @ character? – user-9725874 Jun 23 '19 at 16:36
  • 1
    Hi, @Jesse, I am using this library for email validation - https://www.npmjs.com/package/validator . I know that for the input type email, there is a native browser validation, but this is not a problem now. – gdfgdfg Jun 23 '19 at 16:45

2 Answers2

2

Just setState after you've completely created your errors object. You're setting state more than you need to. You're only really interested in setting the state in this function once you know what all the errors are.

onSubmit = (e) => {
    e.preventDefault();
    const pass = this.state.pass.trim();
    let errors = { pass: '', email: '' };
    if (!isEmail(this.state.email)) {
        errors.email = 'Invalid Email.';
    }
    if (pass.length < 6) {
        errors.pass = 'Password Invalid';
    }
    this.setState({ errors });
}

Using the ... operator here was not the issue. What was likely occurring was that React was bundling your updates together and so each of them was looking at the same state. So if you have the state as

{
    error: {
        email: '',
        pass: '',
    }
}

and you have two updates batched together

setState({errors: {...this.state.errors, email: 'Invalid Email.'}});
setState({errors:{ ...this.state.errors, pass: 'Password Invalid'}});

they will both see the same state and make updates accordingly to produce the following two state updates

this.state = {
    error: {
        email: 'Invalid Email',
        pass: '',
    }
};
this.state = {
    error: {
        email: '',
        pass: 'Password Invalid',
    }
};

of which the second is most likely to be your result because it's the last one that was assigned to the state.

Bash
  • 628
  • 6
  • 19
  • 1
    Agreed, but I think he is starting with a blank error object, not a copy of the old one. and you can setState like `this.setState({ errors })` – Avin Kavish Jun 23 '19 at 16:14
  • Hi, thanks. This is the solution, but I am curious, why doesn't work with `...` operator. – gdfgdfg Jun 23 '19 at 16:52
  • It does work with the `...` operator, but React can batch multiple updates together if `setState` is called several times (or within a life cycle method). When that occurs each update modifies the same version of the state. My changes avoid this scenario by only setting the state once. I've modified my answer to hopefully make it a bit clearer why my method solves your problem. The React docs provide [a different solution](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous) as does this SO [answer](https://stackoverflow.com/a/48731782/6150284). – Bash Jun 24 '19 at 02:52
2

With state function you can do it:

this.setState(prevState => {
   const errors = {};

    if(!isEmail(prevState.email)){
        errors.email = 'Invalid Email.';
    }

    if(prevState.pass.trim().length < 6) {
        errors.pass = 'Password Invalid'
    }

    return {errors: {...prevState.errors, ...errors}}; // combine your new errors with another keys errors if exists
});