typescriptvuetify.js

validate a vuetify form using composition api with typescript


I am trying to validate a form when it is submitted, but having trouble converting examples I have found to use the compostion api and typescript. With the options api it appears that the correct syntax is something like

if (this.$refs.loginForm.validate()) {....}

and with the compostion api, other posts suggest

if (loginForm.value.validate()) {....}

but this is giving me a TS warning that the value is possibly null, and (more importantly) the if() condition always succeeds, even if the form is submitted empty.

My full code is below. The valid variable appears to make no difference, so clearly something else is wrong with the methods I have used.:

<script setup lang="ts">
import { ref } from 'vue'

const loginDialog = ref(false)

const userOrEmail = ref('');
const password = ref('');
const showPass = ref(false);
const loginForm = ref(null);
var valid = true;

const nameRules =   [  (value: string) => !!value || 'Required.', ];
const pwRules =     [  (value: string) => !!value || 'Required.', ];

function submit() {

   if (loginForm.value?.validate()) 
   {
        alert("submitted");
        loginDialog.value = false;
    }
}
</script>

<template>
  <v-dialog v-model="loginDialog" activator="parent" width="300">
  <v-card>
    <v-card-title class="headline black" primary-title>
      Sign in 
    </v-card-title>
    <v-card-text class="pa-5">
      <v-form @submit.prevent="submit" ref="loginForm"  v-model="valid" >
        <v-text-field v-model="userOrEmail"  :rules="nameRules"  label="User name or email" hint="Username will be 3 to 10 characters">
        </v-text-field>
        <v-text-field v-model="password" :append-inner-icon="showPass ? 'mdi-eye' : 'mdi-eye-off'"
          @click:append-inner="showPass = !showPass"   :type="showPass ? 'text' : 'password'" 
          hint="password will be at least 6 characters"
          :rules="pwRules"  label="Password">
        </v-text-field>
        <v-btn color="blue" type="submit" block class="mt-2">    Sign in     </v-btn>
       
      </v-form>
    </v-card-text>
  </v-card>
</v-dialog>
</template>

I have tried uploading a sample to the Vuetify playground but it won;t run, getting errors I don't understand


Solution

  • Calling validate() on a VForm returns a promise, so you need to wait until it resolves:

    async function submit() {
      const {valid, errors} = await loginForm.value.validate()
      if (valid) {
        ...
    

    playground example


    Note that when VForm receives a submit event from the underlying <form> element, it triggers the same validation function you call through the template ref. Then it merges the returned promise with the submit event into a SubmitEventPromise and emits it to the @submit (see code). So you are actually calling validate a second time (and could do without the template ref):

    async function submit(submitEventPromise: SubmitEventPromise) {
      const {valid, errors} = await submitEventPromise
      if (valid) {
        ...
    

    playground example


    Finally, VForm has a :modelValue prop that indicates if the form is valid. In contrast to the validate() function, it is set through a watcher on the registered form inputs, which updates when all inputs have been validated (see code).

    So unless validation on all inputs has been triggered manually, the @update:modelValue event will arrive only after validation() has finished, which by itself makes it an insufficient replacement for the SubmitEventPromise argument in the @submit handler. If you want to rely on :modelValue in the @submit handler without waiting for the promise, make sure that validate() has completed at least once, for example by triggering it manually when the form has loaded.

    playground example