Appearance
VeeValidate
Validating forms
Here is quick guide on how to validate forms using VeeValidate.
1. Create the validator
We start by creating a validator, we can use any of the supported schema validators such as Zod, Yup, or Valibot.
Validators are placed in validators folder and must be suffixed with .validator.ts.
Here we will be using Valibot as our schema validator.
This will be our base schema structure:
javascript
// File: validators/todo.validator.ts
import * as v from 'valibot';
import { toTypedSchema } from '@vee-validate/valibot';
export function useTodoValidatorSchema() {
const baseSchema = v.object({
title: v.string().required('Title is required'),
description: v.string().required('Description is required'),
});
const typedSchema = toTypedSchema(baseSchema);
return { baseSchema, typedSchema };
}Our validator is a simple function that returns the base schema and the typed schema, we used function to make it easier to pass context to the validator if needed.
Next, it must also return typedSchema which is the schema that VeeValidate will use to validate the form and a baseSchema (which is optional) this can be used to extend the schema if needed.
2. Setup the form component
Here will now use the validator in our form component. Assuming we already have a form component, we will now integrate the validator.
In this example, we have a separated form component, the form component is merely a fields container and doesn't have a submit button attached, this is a design choice to make sure we can position the submit button in the parent component anywhere we want.
vue
<!-- File: components/TodoForm.vue -->
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
// Vee-Validate form setup
const { defineField, errors, validate, setFieldValue, values: formValues } = useForm({
name: 'create-todo-form',
validationSchema: createTodoSchema.typedSchema,
validateOnMount: false,
})
async function validateForm(): Promise<{ valid: boolean }> {
const { valid } = await validate();
return { valid }
}
async function getFormValues() {
// A function to return the form values so it can be used in the parent component
}
defineExpose({
validateForm,
getFormValues,
})
</script>This is the general setup for the form component, the validateForm function is used to validate the form and the getFormValues function is used to get the form values. Both must be exposed so it can be used in the parent component.
3. Bind the form fields to the validator
For most field, binding is as straightforward as using the defineField function.
vue
<!-- File: components/TodoForm.vue -->
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
// Vee-Validate form setup
const { defineField, errors, validate, setFieldValue, values: formValues } = useForm({
name: 'create-todo-form',
validationSchema: createTodoSchema.typedSchema,
validateOnMount: false,
})
const [title] = defineField('title');
async function validateForm(): Promise<{ valid: boolean }> {
const { valid } = await validate();
return { valid }
}
async function getFormValues() {
// A function to return the form values so it can be used in the parent component
}
defineExpose({
validateForm,
getFormValues,
})
</script>
<template>
<div>
<InputText :value="title"/>
</div>
</template>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
In this part, we will also cover manually setting values to the field, manually setting field value is used in cases such as a custom dropdown or a picker is used, and the value is not directly binded to the field.
vue
<!-- File: components/TodoForm.vue -->
<script setup lang="ts">
const selectedCategory = ref();
const getSelectedCategoryText = computed(() => {
return selectedData.value?.text;
})
</script>
<template>
<div>
<InputText :value="title"/>
<Dropdown :value="getSelectedCategoryText" @change="handleDropdownChange"/>
</div>
</template>I stripped down the code to focus on the important parts. In the code above, we have a dropdown that is not directly binded to the field, so we need to manually set the value to the field. To do that, depending on what event the dropdown or the component emits, we will use that to set the value to the field.
Option A: Use internal values (such as id) to validate the field.
vue
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
// Vee-Validate form setup
const { defineField, errors, validate, setFieldValue, values: formValues } = useForm({
name: 'create-todo-form',
validationSchema: createTodoSchema.typedSchema,
validateOnMount: false,
})
const [title] = defineField('title');
const [_unusedCategoryId] = defineField('category_id');
const selectedCategory = ref();
const getSelectedCategoryText = computed(() => {
return selectedData.value?.text;
})
const handleDropdownChange = (value) => {
selectedCategory.value = value;
// Manually set the value to the field using the setFieldValue function
setFieldValue('category_id', value.id);
}
</script>
<template>
<div>
<InputText :value="title"/>
<Dropdown :value="getSelectedCategoryText" @change="handleDropdownChange"/>
</div>
</template>ts
import * as v from 'valibot';
import { toTypedSchema } from '@vee-validate/valibot';
export function useTodoValidatorSchema() {
const baseSchema = v.object({
title: v.string().required('Title is required'),
description: v.string().required('Description is required'),
category_id: v.string().required('Category is required'),
});
const typedSchema = toTypedSchema(baseSchema);
return { baseSchema, typedSchema };
}Here, we declared an _unusedCategoryId field to bind the category_id field to the form, this is to ensure that the field is validated even if it is not directly binded or visible to UI. This is useful when we need to validate the internal value of a field.
Option B: Use external values (such as text) to validate the field.
This requires changing the schema to validate the text instead of the id.
vue
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
// Vee-Validate form setup
const { defineField, errors, validate, setFieldValue, values: formValues } = useForm({
name: 'create-todo-form',
validationSchema: createTodoSchema.typedSchema,
validateOnMount: false,
})
const [title] = defineField('title');
const [category] = defineField('category_id');
const selectedCategory = ref();
const handleDropdownChange = (value) => {
selectedCategory.value = value;
// Manually set the value to the field using the setFieldValue function
setFieldValue('category', value.id);
}
</script>
<template>
<div>
<InputText :value="title"/>
<Dropdown :value="category" @change="handleDropdownChange"/>
</div>
</template>ts
import * as v from 'valibot';
import { toTypedSchema } from '@vee-validate/valibot';
export function useTodoValidatorSchema() {
const baseSchema = v.object({
title: v.string().required('Title is required'),
description: v.string().required('Description is required'),
category: v.string().required('Category is required'),
});
const typedSchema = toTypedSchema(baseSchema);
return { baseSchema, typedSchema };
}4. Submitting form and Validating
ts
import * as v from 'valibot';
import { toTypedSchema } from '@vee-validate/valibot';
export function useTodoValidatorSchema() {
const baseSchema = v.object({
title: v.string().required('Title is required'),
description: v.string().required('Description is required'),
category: v.string().required('Category is required'),
});
const typedSchema = toTypedSchema(baseSchema);
return { baseSchema, typedSchema };
}vue
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
// Vee-Validate form setup
const { defineField, errors, validate, setFieldValue, values: formValues } = useForm({
name: 'create-todo-form',
validationSchema: createTodoSchema.typedSchema,
validateOnMount: false,
})
const [title] = defineField('title');
const [category] = defineField('category_id');
const selectedCategory = ref();
const handleDropdownChange = (value) => {
selectedCategory.value = value;
// Manually set the value to the field using the setFieldValue function
setFieldValue('category', value.id);
}
function getFormValues() {
return {
title: title.value,
selectedCategory: selectedCategory.value,
}
}
async function validateForm() {
const { valid } = await validate();
return { valid }
}
defineExpose({
getFormValues,
validateForm
})
</script>
<template>
<div>
<InputText :value="title"/>
<Dropdown :value="category" @change="handleDropdownChange"/>
</div>
</template>vue
<script setup lang="ts">
const todoFormRef = useTemplateRef('todoFormRef');
const submitForm = async () => {
// Call the validateForm function from the form component
const { valid } = await todoFormRef.value.validateForm();
if (valid) {
const formValues = await todoFormRef.value.getFormValues();
// Submit the form
api.createTodo({
title: formValues.title,
description: formValues.description,
category_id: formValues.selectedCategory.category_id,
}))
}
}
</script>
<template>
<div>
<TodoForm ref="todoFormRef" />
<Button @click="submitForm">Submit</Button>
</div>
</template>Resetting the form
To reset the form, we can use the resetForm function from the useForm hook.
vue
<!-- File: components/TodoForm.vue -->
<script setup lang="ts">
import { useCreateTodoValidationSchema } from '@/validators/todo.v.validator';
const createTodoSchema = useCreateTodoValidationSchema();
const selectedCategory = ref();
function clearForm() {
// Manually reset the v-models that are not bound directly to the form
selectedCategory.value = [];
// Proper way of resetting the form. Reset the vee-validate form
resetForm({
values: {
title: '',
description: ''
},
}, { force: true });
}
}
</script>In the code above, we have a clearForm function that resets the form, it manually resets the v-models that are not directly binded to the form and then resets the form using the resetForm function. This way, the form will not show validation errors while the the form is being reset.
We also added a force option to the resetForm function, this is used to force the values to be overwritten if we assigned initialValues to the useForm hook.