Vue 3 + VeeValidate + zod

Vue 3 + VeeValidate + zod

2023-03-31
5 min read

What is Zod?

According to their introduction, Zod is TypeScript-first schema validation with static type inference library. Zod is designed to be as developer-friendly as possible.

Install

bash
npm install zod       # npm
yarn add zod          # yarn
pnpm add zod          # pnpm

Install library in your project. It also supports bun and Deno.

tsconfig.json

JSON
{
  "compilerOptions": {
    "strict": true
  }
}

Your Typescript version should over 4.5+. You also must enable strict in your tsconfig.json. The requirements are written in Documentation.

What is VeeValidate

VeeValidate is form validator library for Vue. It's one of popular library in Vue. It takes care of value tracking, validation, errors, submissions and more

install

bash
npm i vee-validate --save     # npm
yarn add vee-validate         # yarn
pnpm add vee-validate         # pnpm

You can use it with a script tag and a CDN.

html
<script src="https://unpkg.com/vee-validate" />

It will inject VeeValidate global Object once you import library.

How to mix two libraries.

Zod will be used for form rules. On the other hands, Vee-validator will be used for submissions. Let's create a registration form. The example will be written <script setup>. The example code will have three input fields, email, password and confirm password.

Install

Install

bash
npm install @vee-validate/zod       # npm
yarn add @vee-validate/zod          # yarn
pnpm add @vee-validate/zod          # pnpm

Install official vee-validate plugins which allows you to use zod schemas.

Create schema

typescript
const validationSchema = toFormValidator(
    z.object({
      email: z.string({
        required_error: 'Type email'
      })
          .email({
            message: 'Type email',
          })
          .max(30, {
            message: 'Max Email '
          })
          .trim().min(1, {
            message: 'Type email'
          }),
      password: z.string({
        required_error: 'Type password'
      })
        .refine(val => {
        // Not working....
        // Check length
        if (val.length < 8) return false
        // Check lowercase
        if (val.search(/[a-z]/i) < 0) return false
        // Check uppercase
        if (val.search(/[A-Z]/i) < 0) return false
        // Check number
        if (val.search(/[0-9]/) < 0) return false
        // Check special characters
        if (val.search( /^(?=.*[~`!@#$%^&*()--+={}\[\]|\\:;"'<>,.?/_₹]).*$/) < 0) return false
        return true
      }, {
        message: 'Password must include at least one lower letter, one uppercase letter, and special letter',
      }),
      confirmPassword: z.string({
        required_error: 'Type password'
      }).refine(val => val === password.value, {
        message: 'Password is not matched'
      })
    })
)

toFormValidator generates validation schema along with Zod

create form

typescript
const { handleSubmit, errors, isSubmitting, resetForm } = useForm({
  validationSchema,
})

useForm: Creates a vee-validate’s form context and associates any fields inside the same component or its children with it automatically, which you will use to create custom form components and to manage your fields in general. handleSubmit function is used for executing your callback once the returned function like onSubmit. erros is object which contains error messages. If field doesn't have any errors, errors.field will return undefined
isSubmitting indicates the form whether form is submitting or not. resetForm function will reset your all fields.

vue
<Form :validation-schema="schema">
  ...
</Form>

If you would like to use <Form />, add it in HTML section. However, the example will not care of it.

make some fields

typescript
const { value: email } = useField('email')
const { value: password } = useField('password')
const { value: confirmPassword } = useField('confirmPassword')

submit function

typescript
const onSubmit = handleSubmit((values) => {
  console.log(values)
  resetForm()
})

Once you hit the submit button, this function will be called if all rules are passed in Zod schema. parameter "values" include all values of fields.

HTML

vue
<template>
  <form @submit.prevent="onSubmit">
    <label
      class="label"
      for="email"
    >
      <span>email</span>
    </label>
    <input
      id="email"
      v-model="email"
      placeholder="type email"
      type="email"
    >
    <p v-if="errors.email">
      <span>
        {{ errors.email }}
      </span>
    </p>
    <label
      class="label"
      for="password"
    >
      <span>password</span>
    </label>
    <input
      id="password"
      v-model="password"
      placeholder="type password"
      type="password"
    >
    <p v-if="errors.password">
      <span>
        {{ errors.password }}
      </span>
    </p>
    <label
      class="label"
      for="confirmPassword"
    >
      <span>confirm password</span>
    </label>
    <input
      id="confirmPassword"
      v-model="confirmPassword"
      placeholder="type password"
      type="password"
    >
    <p v-if="errors.confirmPassword">
      <span>
        {{ errors.confirmPassword }}
      </span>
    </p>
    <button
      class="btn btn-primary w-full"
      type="submit"
    >
      register
    </button>
  </form>
</template>

Create submit button which has "submit" as a type. You also have to add submit events in your form.

Full Source code

vue
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
import { useField, useForm } from 'vee-validate'

const validationSchema = toTypedSchema(
    z.object({
      email: z.string({
        required_error: 'Type email'
      })
          .email({
            message: 'Type email',
          })
          .max(30, {
            message: 'Max Email '
          })
          .trim().min(1, {
            message: 'Type email'
          }),
      password: z.string({
        required_error: 'Type pasword'
      }).refine(val => {
        // Not working....
        // Check length
        if (val.length < 8) return false
        // Check lowercase
        if (val.search(/[a-z]/i) < 0) return false
        // Check uppercase
        if (val.search(/[A-Z]/i) < 0) return false
        // Check number
        if (val.search(/[0-9]/) < 0) return false
        // Check special characters
        if (val.search( /^(?=.*[~`!@#$%^&*()--+={}\[\]|\\:;"'<>,.?/_₹]).*$/) < 0) return false
        return true
      }, {
        message: 'Password must include at least one lower letter, one uppercase letter, and special letter',
      }),
      confirmPassword: z.string({
        required_error: 'Type password'
      }).refine(val => val === password.value, {
        message: 'Password is not matched'
      })
    })
)
const { handleSubmit, errors, isSubmitting, resetForm } = useForm({
  validationSchema,
})
const { value: email } = useField('email')
const { value: password } = useField('password')
const { value: confirmPassword } = useField('confirmPassword')

const onSubmit = handleSubmit((values) => {
  console.log(values)
  resetForm()
})
</script>
<template>
  <form @submit.prevent="onSubmit">
    <label
      class="label"
      for="email"
    >
      <span>email</span>
    </label>
    <input
      id="email"
      v-model="email"
      placeholder="type email"
      type="email"
    >
    <p v-if="errors.email">
      <span>
        {{ errors.email }}
      </span>
    </p>
    <label
      class="label"
      for="password"
    >
      <span>password</span>
    </label>
    <input
      id="password"
      v-model="password"
      placeholder="type password"
      type="password"
    >
    <p v-if="errors.password">
      <span>
        {{ errors.password }}
      </span>
    </p>
    <label
      class="label"
      for="confirmPassword"
    >
      <span>confirm password</span>
    </label>
    <input
      id="confirmPassword"
      v-model="confirmPassword"
      placeholder="type password"
      type="password"
    >
    <p v-if="errors.confirmPassword">
      <span>
        {{ errors.confirmPassword }}
      </span>
    </p>
    <button
      class="w-full"
      type="submit"
    >
      register
    </button>
  </form>
</template>

Ref

Free open source project made by Youngjin