0

I have a formErrors state like so:

const [formErrors, setFormErrors] = useState({});

And I have an onSubmit() handler like so:

const onSubmit = () => {

   validateForm();

   if(formErrors.length === 0) {
      // submit form
   }
}

The validateForm() function merely populates formErrors if there are errors.

This onSubmit is triggered via a "Submit" button's onClick property.

So what happens is:

  1. I click "Submit".
  2. onSubmit() method gets called.
  3. validateForm() is called.
  4. setFormErrors() is called from within validateForm().
  5. Since setting state is async, it will move to the if(formErrors.length === 0) line which will return true at this stage.
  6. Form gets submitted (this is the unwanted behaviour since it shouldn't be).
  7. setFormErrors() is done and sets formErrors to [error1, error2]. Means formErrors.length should really be > 0.

State not updating immediately is causing a bug where in I am able to submit the form even if there are errors present due to the condition being true first before becoming false (where the onSubmit() func has already finished executing).

How to resolve this issue?

catandmouse
  • 11,309
  • 23
  • 92
  • 150
  • Is `valideForm` async?, if not just return if it's valid or not. And then you can do `if (validateForm()) {......` etc. If it is async, you will want to keep another state for this. – Keith Oct 17 '19 at 15:24
  • @Keith There's a `setFormErrors()` inside `validateForm()`, which is an async func. I am setting the errors in the local state. – catandmouse Oct 17 '19 at 15:28
  • 1
    `setFormErrors` is not `async`, it's just that it will cause React to do a re-render. And in this case means it will be fine to return your `validateForm` result inside you `onSubmit`. If the form is invalid, you can then reject your submit and your setFormErrors will update your UI,.. – Keith Oct 17 '19 at 15:32
  • 1
    Maybe validateForm can return an array of errors instead and have your onSubmit set the state or submit the form. – HMR Oct 17 '19 at 15:57
  • @HMR and @Keith Tried and it works. Just bummed that there's no reliable way to wait for the the state change before continuing into the next function. BTW, @Keith think setting state via `useState` is async as per https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately – catandmouse Oct 17 '19 at 17:00
  • @catandmouse setState is not `async`.. It just updates the state, and React schedules a repaint. The repaint of course can be done in the future, but the setState itself is not async. The reason the state doesn't update immediately is because the new state that you have set, gets passed to the next render not the current one. This is not the same as saying setState is `async`. – Keith Oct 17 '19 at 17:29
  • @Keith The new sate is set on re render because you get the state with `useState` so `const [someVal, setSomeVal] = useState` will set someVal but `setSomeVal('anything')` would not change `someVal` until you re run useState at next render. – HMR Oct 17 '19 at 18:24
  • @HMR Not sure what your saying about what I'm saying is incorrect. But anyway, setState is NOT `async`.. – Keith Oct 17 '19 at 19:38
  • @Keith All other sources I read says that setState is async for performance purposes. Can show related source regarding this? – catandmouse Nov 21 '19 at 01:43
  • @catandmouse It's `async` but not in the traditional sense. The performance part is that it just queue's up your setStates, so that multiple changes don't cause multiple renders. You can use a callback to setState, but if your doing something like that then I'd say it's something funky your doing. For me, a fail safe way of thinking of setState is that's your setting up what your state of your application is going to be at the `next` render. – Keith Nov 21 '19 at 15:56

0 Answers0