vee-validate icon indicating copy to clipboard operation
vee-validate copied to clipboard

useForm - meta.valid false even tho there are no errors?

Open datWeazel opened this issue 1 year ago • 10 comments

What happened?

I got multiple Forms that get shown one after the other. If I am at step 5 or whatever and I get back to a previous step like step 2 the form of step 2 gets shown but it's no longer valid even tho it was before.

Even if I then change the values of the form again the meta.valid state doesn't get updated while the errors get set correctly.

interface SchadenursacheData {
  schadenursache: SchadenursacheTypes;
  valid: boolean;
}

const { handleSubmit, meta, values, errors } = useForm<SchadenursacheData>({
  validationSchema: object({
    schadenursache: string().required("Bitte angeben"),
  }),
});

This is the "failed state" after returning to the form:

Screenshot 2024-01-12 at 09 43 07

Reproduction steps

Sorry I don't have reliable repro steps outside our codebase.

Version

Vue.js 3.x and vee-validate 4.x

What browsers are you seeing the problem on?

  • [X] Firefox
  • [X] Chrome
  • [X] Safari
  • [ ] Microsoft Edge

Relevant log output

No response

Demo link

Code of Conduct

  • [X] I agree to follow this project's Code of Conduct

datWeazel avatar Jan 12 '24 08:01 datWeazel

The valid flag is independent from the errors existence, because a silent validation is run initially.

Do you mind creating an example for this?

logaretm avatar Jan 13 '24 15:01 logaretm

Could take a bit to repro. Will report back once I have a good reproduction.

But besides that is there any way to debug why the valid flag is false? Instinctively you'd think that if there are no errors, the form is valid.

datWeazel avatar Jan 15 '24 07:01 datWeazel

You are right that's a perfectly valid way to think about it but from usability perspective, let's say you want to disable the submit until the form is valid. Now if the valid flag is just a reflection of errors existence, you can never do that because we didn't validate the form yet so we have no idea if it is valid or not and thus it is assumed to be valid when it can be the other way around.

Form validity has actually 3 values, valid, invalid and unknown. We have no way to represent the last value with booleans which is why it can never satisfy all needs. It's a tricky subject which is hard to get right.

logaretm avatar Jan 16 '24 22:01 logaretm

I see. It doesn't really reflect my situation though I guess. Once I change any value in the form the validation is triggered. Let's say I input regular text in a field with a numbers-only restriction. An error is placed inside the error object of useForm and shown to the user. So validation must have taken place. Isn't it safe to assume that the valid value in this case should be invalid and if I then correct the input to be a number the value to be valid again?

Just trying to understand the meta object correctly so I got an easier time in the future. :) Thank you so far.

(Btw. still working on that repro, don't have much time atm but will get back to you.)

datWeazel avatar Jan 17 '24 07:01 datWeazel

got the same error and this entirely breaks my apps, since i also use meta.valid to check if the form has no error then the user can submit the form. i think it is instinctively meta.valid is refer to if the form is valid and has no error. the problem i ecounter is, using a modal with input, when initially the modal render, it has no problem, but the thing happen when user close the modal and then re open it, the validation did not work and user suddenly cannot submit the form. in my cases the user does not need to input anything, just submitting the modal

hi-reeve avatar Feb 02 '24 04:02 hi-reeve

I have the same issue. Basically, meta.valid is useless, so I'm using !!Object.keys(errors).length to check whether the form is valid, which is super ugly and non-intuitive.

I understand that it might not be ideal to use meta.valid as an indicator for the presence of errors, but then there should be a prop that does exactly that because that's how most people use it and it's the intuitive way to think about form validation.

This is how I'd like to think: Does my form have errors, if so block submission, otherwise, it's safe to continue. I should assume that at every update:model-value and initially when the form is rendered the validation is run, and if at any point there's an error in my form, it should be inside the errors object. If I ever second-guess myself, it should be due to a pending (meta.pending) validation, and nothing else. Not being able to assume these intuitive rules is... non-intuitive at best.

Also, I notice that a lot of times meta.pending is true, which is weird, like, shouldn't it only be true during the async operation of validation?

theonelucas avatar Mar 15 '24 15:03 theonelucas

Got the same issue. The problem in my case was the incorrect value validation: I had an required field wich were a select input. After selecting a value errors were empty and valid was false. So I did something crazy, I had computed variable inside disabled property on my submit button. In the computed callback I ran validate + logs. This caused the computed to be triggered every tick, AND I got the errors with message that my required field is not set. But the field is in values and also have correct value.

UPD: At the end I found out, that the error appeared after some code with 'useField' were moved to separat component. I think if useField is too deeply nested - this error can appear :)

UPD2: The issue were in v-if which waited for the data. So if useField is used later, the error appears.

senyaak avatar May 16 '24 13:05 senyaak

I hit this error with the following minimal reproducible example:

const validationSchema = toTypedSchema(
  z.object({
    day: z.string().optional(),
    month: z.string().optional(),
    year: z.string().optional(),
  }).refine((input) => {
    if ([input.day, input.month, input.year].some(Boolean))
      return [input.day, input.month, input.year].every(Boolean)
    return true
  }, {
    message: 'Please fill out all fields for patient birth date',
    path: ['day', 'month', 'year'],
  }),
)

const { values, errors } = useForm({
  validationSchema
})
const isFormValid = useIsFormValid()

I use zod.refine to make the date fields either all optional or all required. After more debugging, the errors object only populates if the day field is dirtied. month and year never update with the error. I'm able to chain multiple refine()s, each with the path array being a different field and it works as expected, but having each path in one array is breaking the non-first places. Maybe I'm misunderstanding the purpose of path in the zod refine call, but I still get isFormValid is false with errors being empty.

Jtcruthers avatar May 20 '24 20:05 Jtcruthers

I ran into this issue several times and can't trace what is happening, nice investigation @senyaak [comment] and @Jtcruthers [comment]! That helped me feel more confident I'm not doing something wrong, I just have a wrong understanding of vee-validate.

Details of my observation What I noticed just now was upon page load `meta.valid` was false with no errors. The form was correctly invalid: a required field was empty. It was only when I put my cursor into the input, typed a value, cleared the value, and blurred the field that I was able to see an error.

Ultimately I'm OK with that behavior -- if the form is invalid it should be invalid. However, the lack of observability on WHY the form was invalid caused me significant time loss. I have a complex form (with complex yups) so I made a bad assumption that my yup was wrong, when really the yup was correct, but the data was truly invalid; I just couldn't see WHICH data was invalid.

My expectation is if meta.valid is false there would be some way to observe why its invalid. What way can that be observed? I assumed errors would have it but I see comments explaining why that might not be the right place. Perhaps there is some hook/callback where the observation can happen?

josephdpurcell avatar May 23 '24 22:05 josephdpurcell

We have a vue-devtools plugin that gets auto-installed it does help with cases of hidden errors or why a form isn't submitting or why it is valid.

But it doesn't help with that first initial case, I'm starting to lean more and more towards making meta.valid be just a reflection of errors existence and moving the error display logic to user-land.

This would eliminate a lot of issues i'm getting with "why validation ran" and allows validation triggers to truly reflect what they do.

Example of that thought:

<script>
const { value, errorMessage, meta } = useField('field');
</script>

<template>
	<input v-model="value">
    <span v-if="meta.touched || meta.dirty">{{ errorMessage }}</span>
</template>

It is more verbose but removes that "confusion" from how it works under the hood.

For the zod refines, I think they are confusing for the most part because they run if all the object properties exist or had a similar limitation. I don't remember correctly but I would not include them with this issue as the same one.

If I go with this it would be at v5 since this is a major change.

logaretm avatar May 28 '24 21:05 logaretm