0

I am trying to keep track of multiple states (error handling in this instance), but for reasons unknown at this stage (lack of understanding I think) I only seem able to keep the state of the last error function call:

export const SignUp = () => {
  const [errors, setErrors] = useState({});
  
  const validateFirstName = () => {
    if (formData.firstName === undefined) {
      setErrors(prevState => ({
        ...prevState.firstName,
        firstName: 'First Name is required',
      }));
      return false;
    } else if (formData.firstName.length < 3) {
      setErrors(prevState => ({
        ...prevState.firstName,
        firstName: 'First Name is too short',
      }));
      return false;
    }
    return true;
  };

  const validateLastName = () => {
    if (formData.lastName === undefined) {
      setErrors(prevState => ({
        ...prevState.lastName,
        lastName: 'Last Name is required',
      }));
      return false;
    } else if (formData.lastName.length < 1) {
      setErrors(prevState => ({
        ...prevState.lastName,
        lastName: 'Last Name is too short',
      }));
      return false;
    }
    return true;
  };

  const formSubmitHandler = e => {
    e.preventDefault();
    validateFirstName();
    validateLastName();
  };
}

So in my formSubmitHandler I call each method in turn. Is this wrong? Do they need to be async for example? Or does the issue lie within my setError function? Am I not setting the updated state correctly?

When I log out errors I only ever see lastName:

{"errors": {"lastName": "Last Name is required"}}

What can I try to resolve this?

halfer
  • 19,824
  • 17
  • 99
  • 186
Richlewis
  • 15,070
  • 37
  • 122
  • 283

4 Answers4

1

This is where your problem is:

  {
    ...prevState.lastName,
    lastName: 'Last Name is too short',
  }

you're only passing the lastName (or firstName in the other function) to your new state, so it doesn't have the other values anymore.

Just do:

  {
    ...prevState,
    lastName: 'Last Name is too short',
  }

for all of them, and you should now have all the values in your Errors state

user3812411
  • 342
  • 3
  • 17
  • oh thats worked, amazing!. thank you so much... just need to figure out why this works now, not quite sure i fully understand the flow – Richlewis Dec 20 '21 at 16:29
  • So, what you're doing is using one state object for all the errors. so, the object looks like {error1: "", error2: "", ...}. Now, when you update your state, prevState is the entire current object with all the errors, so you pass {...prevState} and replace the one you want to change using {...prevState, error1: "new value"}. – user3812411 Dec 20 '21 at 16:45
  • Got it, that makes more sense now, thank you – Richlewis Dec 20 '21 at 16:47
  • Might need a separate question for this, but how would i remove the key from the error object if i needed to? – Richlewis Dec 20 '21 at 16:56
  • 1
    @Richlewis copy the object into a constant (const tmp = errors), delete tmp.yourKey, setErrors(tmp). So, if you're removing lastName for example, delete tmp.lastName then setErrors(tmp) – user3812411 Dec 20 '21 at 17:25
1

Different ways you can go about doing this. You could also just batch your responses from the validators and set the state in one go. This will also reduce the rerending of your app.

Working Example: https://snack.expo.dev/@tnr_c/funny-raspberries

TnR
  • 99
  • 4
  • Thank you for providing the example, I think I will try this. So to understand correctly lets say my form has 10 fields to validate, with my current implementation I will be rendering 10 times? whereas with your example I can just re render once and still validate all fields? – Richlewis Dec 20 '21 at 16:41
  • 1
    Changes to state/props will cause the component to rerender. However, react is smart with event handlers and batchs them. You can find more information here: https://stackoverflow.com/a/48610973/458193. If you are batching all your validators to an event like a button press in the example you could handle it in the manner you had with the changes @user3812411 suggested and it will work fine. – TnR Dec 20 '21 at 16:58
0

Put the functions the same order as your form ,so it check each of them 1st before going to the next one.

Arti
  • 118
  • 7
  • Thank You @Arti, so I would set this within my `formSubmitHandler` ? – Richlewis Dec 20 '21 at 14:16
  • @Richlewis useEffect is used outside , its like a componentDidMount function , separate from others, and don't forget to import useEffect from react ! – Arti Dec 20 '21 at 14:19
  • @Richlewis leaving this here it might help you ! [useEffect](https://reactjs.org/docs/hooks-effect.html) – Arti Dec 20 '21 at 14:24
  • Thank You :-), will spend some time reading, did try your above options, first one ended in an infinite loop, second one did not appear to change anything.. will keep looking though – Richlewis Dec 20 '21 at 14:25
  • Would you mind putting validateFirstName(); below validateLastName(); in the submit function? if in the errors prints {"errors": {"firstName": "First Name is required"}} its because the lastname is replacing the useState and you will need to add , to state instead of replacing it. – Arti Dec 20 '21 at 14:35
  • Yes as suspected switching them around results in firstName being printed, so the way I am updating is not working as expected. I am replacing rather than updating. – Richlewis Dec 20 '21 at 15:01
  • I would suggest then to put your fuctions the same order as your form , i will update my answer with your ideal then.hope i helped somehow. – Arti Dec 20 '21 at 15:04
  • either way i suggest you explore the useEffect function to solve this issue in the future! – Arti Dec 20 '21 at 15:10
0

State change in react are async so:

validateFirstName();
validateLastName();

Where you update the state of errors will lead to unpredictable results. There are few options to do form validations in react, to keep things basic I would start with: https://reactjs.org/docs/hooks-reference.html#usereducer

Eran
  • 1,628
  • 16
  • 31