VeeValidate and YUP async lazy email validation sending requests for every input in form on background

Viewed 437

I'm trying to add lazy e-mail unique validator to my registration form. It works but when i type something in other fields (including e-mail field also) unique validator keeps running and sending request to server repeatedly. I think form level validation causes it but i couldn't find a way to turn off it. How to prevent validating email field in form level for annoying validation requests?

Here is the script setup:

<script setup lang='ts'>
// E-mail unique validator (adding method to YUP's string)
addMethod(string, 'unique', function unique(message: string) {
  return this.test('unique', message, function (value: string) {
    const { path, createError } = this;

    return new Promise((resolve, reject) => {
      EventService.checkEmailUniqueness(value).then(() => {
        resolve(true)
      }).catch(() => {
        reject(createError({ path, message }))
      })
    });
  })
})

// YUP validation schema
const userLoginSchema = object({
  firstname: string().required(t('fieldRequired')),
  lastname: string().required(t('fieldRequired')),
  email: string().email(t('emailRequired')).unique(t('emailInUse')).required(t('fieldRequired')),
  password: string().required(t('fieldRequired')).min(8, t('minPasswordLength', {min: 8})),
  password2:
      string().required(t('fieldRequired')).min(8, t('minPasswordLength', {min: 8})).oneOf([refy('password')],
      t('passwordNotMatch')),
})

// Form and fields
const { handleSubmit, errors } = useForm({
  validationSchema: userLoginSchema,
  validateOnMount: false,
});

const { value: firstname } = useField('firstname')
const { value: lastname } = useField('lastname')
const { value: email, handleChange } = useField('email')
const { value: password } = useField('password')
const { value: password2 } = useField('password2')
</script>

Form template (built using vuetify):

<template lang='pug'>
    v-card(width='300').mx-auto
      v-card-text
        v-form(@submit.prevent='register')
          v-text-field(v-model='firstname' :label='$t("firstname")'
            :error-messages='errors.firstname' required)
          v-text-field(v-model='lastname' :label='$t("lastname")'
            :error-messages='errors.lastname' required)
          v-text-field(:model-value='email' :label='$t("email")'
            type='email' :error-messages='errors.email' @change='handleChange' required)
          v-text-field(v-model='password' :label='$t("password")'
            :append-inner-icon='showPassword ? "mdi-eye": "mdi-eye-off"'
            :type='showPassword ? "text": "password"'
            @click:append-inner='showPassword = !showPassword' :error-messages='errors.password'
            required)
          v-text-field(v-model='password2' :label='$t("repeatPassword")'
            :append-inner-icon='showPassword ? "mdi-eye": "mdi-eye-off"'
            :type='showPassword ? "text": "password"'
            @click:append-inner='showPassword = !showPassword' :error-messages='errors.password2'
            required)
          div.text-body-1.text-red {{ err }}
          v-btn(v-if='!loading' color='success' variant='contained-text'
            type='submit').mt-5 {{ $t('register') }}
          v-progress-circular(v-if='loading' color='info' indeterminate="").mt-5
    </template>
2 Answers

I couldn't figure out how to disable repeated validation on email field but the answer here let me chain my validations (normally validations run in parallel) and get rid of ridiculous requests.

Here is the edited version of schema above, hope it helps:

const sequence = <T>(
    name: string,
    ...schemas: ReadonlyArray<BaseSchema>
): TestConfig<T> => {
  return {
    name,
    test: async (value, context) => {
      try {
        for (const schema of schemas) {
          await schema.validate(value, { context })
        }
      } catch (e) {
        if (e instanceof ValidationError) {
          return context.createError({ message: e.message })
        }
        throw e
      }
      return true
    },
  }
}

const userLoginSchema = object({
  firstname: string().required(t('fieldRequired')),
  lastname: string().required(t('fieldRequired')),
  email: string().test(
      sequence('email',
          string().required(t('fieldRequired')).email(t('emailRequired')),
          string().test( // You can also write your custom validations via addMethod like in original post
              {
                name: 'unique',
                message: t('emailInUse'),
                test: async (value: string) => {
                  EventService.checkEmailUniqueness(value).then((response) => {
                    return response.data.is_available
                  }).catch((e) => { throw e })
                }
              }
          )
      )
  ),
  password:
      string().matches(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?\d).{8,}$/,
          t('passwordMatch')).required(t('fieldRequired')).min(8,
          t('minPasswordLength',
      {min: 8})),
  password2:
      string().required(t('fieldRequired')).oneOf([refy('password')], t('passwordNotMatch')),
})

Instead of putting your validation rules inside a form validation schema, pass them independently as the second parameter of each useField call:

const { handleSubmit, errors } = useForm({
  // ditch the validationSchema
  // validationSchema: userLoginSchema,
  validateOnMount: false,
});

// Declare each field and provide validators
const { value: firstname } = useField('firstname', 'required|customGlobalVal')
// ... same with your other fields

After this, changing a field should not trigger validations in the others, unless you manually do it.

Related