skip to Main Content

Problem Statement : I just migrated my Vue 2 application (Contains Vuetify 2) into Vue 3 (with Vuetify 3). After migrating, validate() method on form is not working as expected. It always return truthy value if still there are errors in the form fields.

(this.$refs.myForm as any & { validate: () => boolean }).validate()

OR

(this.$refs.myForm as any).validate()

Above line of code always returns promise (Assuming a truthy value).

Here is the link of Vuetify playground (Vue: 3.3.4 and Vuetify: 3.3.7)

What I tried so far ?

I tried to find the root cause of the issue and come up with the conclusion that this.$refs.myForm.validate() returns a promise (I think compiler assumes that as a truthy value).

I think I can achieve this by adding the v-model attribute in the form element and then on submit, I can check the form v-model value. Is this approach correct ? Or I have to change something in the original approach to make it work ?

Here is the playground link as per this approach.

Update :

If I am using both the solutions together, It is working as per the expectation (Showing validation errors and preventing the submit if form has any error).

Template :

<v-form ref="myForm" v-model="valid">

Script :

On submit button @click event

if ((this.$refs.myForm as any & { validate: () => boolean }).validate() && this.valid) {
  ...
}

Above solution works fine but Still thinking about best approach. If I will use validate using VForm v-model, It is only preventing the submit if there is any errors but not highlighting the errors.

2

Answers


  1. In Vuetify 3, the validate method returns the promise which can get resolved either using await or chaining the response using .then(). An example in documentation also demonstrates that see here.

    So, one approach for this problem could be using a ref variable on the form and on submit, simply call the validate() method and chain its response to extract the valid param which tells the validation status.

    I used script setup in the demo. Here is the possible solution-

    <template>
      <v-form ref="myForm">
        <v-container>
          <v-row>
            <v-col cols="12" md="4">
              <v-text-field
                v-model="firstname"
                :rules="nameRules"
                :counter="10"
                label="First name"
                required
              ></v-text-field>
            </v-col>
          </v-row>
          <v-btn type="submit" color="primary" @click="submitForm">Submit</v-btn>
        </v-container>
      </v-form>
    </template>
    
    <script setup lang="ts">
      import { ref } from 'vue'
      const myForm = ref();
      const firstname = ref(null);
      const nameRules = [
        value => {
          if (value) return true
          return 'Name is required.'
        },
        value => {
          if (value?.length <= 10) return true
          return 'Name must be less than 10 characters.'
        },
      ]
      const submitForm = () => {
        myForm.value?.validate().then(({valid: isValid}) => {
          console.log(isValid);
        })
      }
    </script>
    

    Here is the working demo.

    Hope this helps.

    Login or Signup to reply.
  2. Hmm, I am pretty sure your solution does not work the first time, as the @click handler on the submit button will run before the submit event is triggered on the VForm or any other global validation could finish. So that means on the first click, when you do:

    if ((this.$refs.myForm as any & { validate: () => boolean }).validate() && this.valid) {
      ...
    }
    
    • the this.$refs.myForm.validate() will always be true, since validate() returns a promise, and Boolean(new Promise()) is true
    • this.valid will be updated through the v-model after validate() has finished or when validation on every input has been triggered manually (this is how it is implemented)

    On subsequent clicks, validate() will have run at least once, so :modelValue will reflect the current validation status. So at that point, it works.

    Using nextTick as you suggest will make execution wait, but for a more or less unrelated event. Waiting for the promise from validation() is the more direct and reliable approach and makes more sense semantically, so I would suggest to use that.


    So I think the best solution is the one suggested by Vuetify, where you await the promise passed to the @submit handler (which is also more robust than using @click on the submit button, as it covers programmatic submits too):

    async function submit(submitEventPromise: SubmitEventPromise) {
      const {valid, errors} = await submitEventPromise
      if (valid) {
        ...
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search