title | description | order | next |
---|---|---|---|
Validation |
Field-level and form-level validation and validation behavior and error messages with composition API |
2 |
guide/composition-api/handling-forms |
vee-validate handles complex validations in a very easy way, it supports synchronous and asynchronous validation, and allows defining rules on the field-level or on the form level using validation schemas with built-in support for yup.
You will be using the following composition functions to validate your forms:
useField
: Creates a form field with its validation state, you will use this inside your custom input components.useForm
: Creates a vee-validate's form context and associated any fields created withuseField
inside the same component or its children with it automatically, you will use to create custom form components and to manage your fields in general.
There are tons of other composition API functions, check them out in the API reference.
This is the most basic example with the composition API, you can create a simple field and validate it in a couple of lines:
You can define validation rules for your fields using the useField
composition API function, your rules can be as simple as a function that accepts the current value and returns an error message.
<template>
<div>
<input v-model="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
</template>
<script>
import { useField } from 'vee-validate';
export default {
setup() {
function isRequired(value) {
if (value && value.trim()) {
return true;
}
return 'This is required';
}
const { errorMessage, value } = useField('fieldName', isRequired);
return {
errorMessage,
value,
};
},
};
</script>
The validation happens automatically when value
binding changes, meaning you can use useField
to trigger validation for any kind of data and not just for inputs.
yup is a very popular, simple and powerful data validation library for JavaScript, you can use it in combination with vee-validate, You can use yup
to define your validation rules for that field:
<template>
<div>
<input v-model="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
</template>
<script>
import { useField } from 'vee-validate';
import * as yup from 'yup';
export default {
setup() {
const { errorMessage, value } = useField('fieldName', yup.string().required().min(8));
return {
value,
errorMessage,
};
},
};
</script>
For more information on the useField
function, read the API reference.
Instead of providing validations for each field individually which can clutter your code, you can instead define the validation schema using the useForm
function by passing a validationSchema
option. Each field will automatically be associated with it and will be validated accordingly.
A simple validation schema can be an object containing field names as keys and validation functions as the value for those keys.
<template>
<div>
<input name="email" v-model="email" />
<span>{{ emailError }}</span>
<input name="password" v-model="password" type="password" />
<span>{{ passwordError }}</span>
</div>
</template>
<script>
import { useForm, useField } from 'vee-validate';
export default {
setup() {
// Define a validation schema
const simpleSchema = {
email(value) {
// validate email value and return messages...
},
name(value) {
// validate name value and return messages...
},
};
// Create a form context with the validation schema
useForm({
validationSchema: simpleSchema,
});
// No need to define rules for fields
const { value: email, errorMessage: emailError } = useField('email');
const { value: password, errorMessage: passwordError } = useField('password');
return {
email,
emailError,
password,
passwordError,
};
},
};
</script>
Fortunately there is already a very neat way to build validation schemas for your forms by using yup
, it allows you create validation objects like this:
const schema = yup.object({
email: yup.string().required().email(),
name: yup.string().required(),
password: yup.string().required().min(8),
});
vee-validate has built-in support for yup schemas, You can pass your schemas to the useForm
function using the same validationSchema
option:
<template>
<div>
<input name="email" v-model="email" />
<span>{{ emailError }}</span>
<input name="password" v-model="password" type="password" />
<span>{{ passwordError }}</span>
</div>
</template>
<script>
import { useForm, useField } from 'vee-validate';
import * as yup from 'yup';
export default {
setup() {
// Define a validation schema
const schema = yup.object({
email: yup.string().required().email(),
password: yup.string().required().min(8),
});
// Create a form context with the validation schema
useForm({
validationSchema: schema,
});
// No need to define rules for fields
const { value: email, errorMessage: emailError } = useField('email');
const { value: password, errorMessage: passwordError } = useField('password');
return {
email,
emailError,
password,
passwordError,
};
},
};
</script>
For more information on the useForm
function, read the API reference.
There are a couple of optimization caveats when it comes to using yup
schemas to validate your forms, be sure to check the best practices guide.
There is an official integration available for Zod validation that you can use as a drop-in replacement for yup
. Check the zod integration page.
You can have reactive form schemas using computed
if you are looking to create dynamic schemas using either yup
or a validation object.
<template>
<input name="password" v-model="password" type="password" />
<span>{{ passwordError }}</span>
</template>
<script>
import { computed, ref } from 'vue';
import { useForm, useField } from 'vee-validate';
import * as yup from 'yup';
export default {
setup() {
const min = ref(0);
const schema = computed(() => {
return yup.object({
password: yup.string().min(min.value),
});
});
// Create a form context with the validation schema
useForm({
validationSchema: schema,
});
const { value: password, errorMessage: passwordError } = useField('password');
return {
password,
passwordError,
};
},
};
</script>
When the validation schema changes, only the fields that were validated at least once will be re-validated, the other fields won't be validated to avoid aggressive validation behavior.
This is only relevant to the useField
and useForm
API
By default vee-validate runs validation whenever the value
ref changes whether it was bound by a v-model
or changed in your code:
const { value } = useField('fieldName', isRequired);
// validation WILL be triggered
value.value = 'something';
You can disable that behavior by passing a validateOnValueUpdate
option set to false
:
const { value } = useField('fieldName', isRequired, {
validateOnValueUpdate: false,
});
// validation WILL NOT trigger
value.value = 'something';
useField()
composition function is not concerned with any events, it only validates whenever the value
ref changes. However it gives you everything you need to setup your own validation experience.
The useField
function exposes some handler functions, each handles a specific aspect of the validation experience:
handleChange
: Updates the field value, triggers validation in all cases.handleInput
: Updates the field value, triggers validation ifvalidateOnValueUpdate
option is enabled.handleBlur
: Updates themeta.touched
flag, doesn't trigger validation.
const { handleChange, handleBlur, handleInput } = useField('someField');
In this example we are validating on input
event (when user types), which would make the validation aggressive:
<template>
<div>
<input @input="handleChange" :value="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
</template>
<script>
import { useField } from 'vee-validate';
export default {
setup() {
function isRequired(value) {
// ...
}
const { errorMessage, value, handleChange } = useField('fieldName', isRequired);
return {
errorMessage,
value,
handleChange,
};
},
};
</script>
With a slight adjustment we can make our validation lazy by changing the listener to @change
(validates when user leaves the control):
<div>
<input @change="handleChange" :value="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
If we wanted to validate on blur
as well, you can simply add handleChange
as a handler for it:
<div>
<input @change="handleChange" @blur="handleChange" :value="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
As you can see, the useField
doesn't really care which events you use handleChange
for. This allows for greater flexibility that's not possible with the <Field>
component, not as straightforward at least.
Consider this validation experience:
- Validate on Change/Blur initially (when user leaves the control), let's call this lazy mode.
- If the field is invalid, switch the validation to validate on input (when user types), let's call this aggressive mode.
- If the field is valid, go back to "lazy" mode, otherwise be "aggressive".
Implementing this requires some knowledge about how the v-on
(we can bind objects on it) handler works and the computed
function. It can be as easy as this:
<template>
<div>
<input v-on="validationListeners" :value="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
</template>
<script>
import { computed } from 'vue';
import { useField } from 'vee-validate';
export default {
setup() {
function isRequired(value) {
// ...
}
const { errorMessage, value, handleChange, handleInput } = useField('fieldName', isRequired, {
validateOnValueUpdate: false,
});
const validationListeners = computed(() => {
// If the field is valid or have not been validated yet
// lazy
if (!errorMessage.value) {
return {
blur: handleChange,
change: handleChange,
input: handleInput,
};
}
// Aggressive
return {
blur: handleChange,
change: handleChange,
input: handleChange, // only switched this
};
});
return {
errorMessage,
value,
validationListeners,
};
},
};
</script>
Check the working example here:
You can display error messages using either useField
, or useForm
.
You've already seen how to display errors with useField
, by using the errorMessage
ref:
<template>
<div>
<input v-model="value" type="text" />
<span>{{ errorMessage }}</span>
</div>
</template>
<script>
import { useField } from 'vee-validate';
export default {
setup() {
function isRequired(value) {
// ...
}
const { errorMessage, value } = useField('fieldName', isRequired);
return {
errorMessage,
value,
};
},
};
</script>
In addition to this, you can get all errors for the field using the errors
ref which contains multiple error messages if applicable:
<template>
<div>
<input v-model="value" type="text" />
<template v-if="errors.length">
<p>Please correct these errors:</p>
<ol>
<li v-for="message in errors" :key="message">{{ message }}</li>
</ol>
</template>
</div>
</template>
<script>
import { useField } from 'vee-validate';
export default {
setup() {
function isRequired(value) {
// ...
}
const { errors, value } = useField('fieldName', isRequired);
return {
errors,
value,
};
},
};
</script>
If you have multiple fields, it can be cumbersome to rename each errorMessage
ref so they don't conflict with each other. Instead you can use the errors
returned by useForm
to display messages for all fields.
<template>
<div>
<input name="email" v-model="email" />
<span>{{ errors.email }}</span>
<input name="password" v-model="password" type="password" />
<span>{{ errors.password }}</span>
</div>
</template>
<script>
import { useForm, useField } from 'vee-validate';
import * as yup from 'yup';
export default {
setup() {
// Define a validation schema
const schema = yup.object({
email: yup.string().required().email(),
password: yup.string().required().min(8),
});
// Create a form context with the validation schema
const { errors } = useForm({
validationSchema: schema,
});
// No need to define rules for fields
const { value: email } = useField('email');
const { value: password } = useField('password');
return {
errors,
email,
password,
};
},
};
</script>
More often than not, your fields will have names with underscores or shorthands which isn't very nice when showing in error messages, for example you might have specific encoding to your field names because they might be generated by backend. Ideally you want to avoid having messages like:
The down_p is required
And instead show something more meaningful to the user
The down payment is required
You can do this in two ways depending on which validators you are using (yup or global validators).
With yup it is very straightforward, you just need to call label()
after defining your field's validations either in field level or form level:
const schema = Yup.object({
email_addr: Yup.string().email().required().label('Email Address'),
acc_pazzword: Yup.string().min(5).required().label('Your Password'),
});
Here is a live example:
If you are interested on how to do the same for global validators check the i18n guide
Each field has meta data associated with it, the meta
property returned from useField
contains information about the field:
valid
: The current field validity, automatically updated for you.touched
: If the field was touched, can be updated withsetTouched
onuseField
return value.dirty
: If the field value was updated, you cannot change its value.pending
: If the field's validations are still running, useful for long running async validation.initialValue
: The field's initial value, it isundefined
if you didn't specify any.
const { meta } = useField('fieldName');
meta.dirty;
meta.pending;
meta.touched;
meta.valid;
meta.initialValue;
This is the typescript interface for a field's meta value
interface FieldMeta {
dirty: boolean;
pending: boolean;
touched: boolean;
valid: boolean;
initialValue: any;
}
In the following example we use the meta.dirty
flag to check if the field value was changed or not
<template>
<input v-model="value" type="text" />
<button :disabled="!meta.dirty">Submit</button>
</template>
<script>
import { useField } from 'vee-validate';
export default {
setup() {
// Provide initial value to make `meta.dirty` accurate
const { value, meta } = useField('fieldName', undefined, {
initialValue: '',
});
return {
errorMessage,
value,
meta,
};
},
};
</script>
Notice in the previous example, we passed an initialValue
, this is because the default field value is undefined
which may cause unexpected meta.dirty
results.
To get accurate results for the meta.dirty
flag, you must provide an initial value to your field even if the values are empty.
To reduce the verbosity of adding an initialValue
prop to each field, you could provide the initialValues
prop to your useForm
call instead.
Since the meta.valid
flag is initially true
(because it just means there are no errors yet), it would cause problems if you have a "success" UI state an indicator.
To avoid this case you should combine the valid
flag with either meta.dirty
or meta.touched
to get accurate representation.
Forms also have their own meta
value containing useful information about the form, it acts as an aggregation of the metadata for the fields inside that form.
const { meta } = useForm();
meta.value.dirty;
meta.value.pending;
meta.value.touched;
meta.value.valid;
meta.value.initialValues;
valid
: The form's validity status, will betrue
if the errors array is empty initially, but will be updated once the form is mounted.touched
: If at least one field was blurred (unfocused) inside the form.dirty
: If at least one field's value was updated.pending
: If at least one field's validation is still pending.initialValues
: All fields' initial values, packed into an object where the keys are the field names.
Here is a similar example where we disable the form's submit button if no value was changed, the same caveat applies is that we have to provide an initialValues
prop to useForm
to make sure meta.dirty
works accurately:
<template>
<form @submit="submit">
<input v-model="value" type="text" />
<button :disabled="!meta.dirty">Submit</button>
</form>
</template>
<script>
import { useField, useForm } from 'vee-validate';
export default {
setup() {
const { meta, values } = useForm({
initialValues: {
email: '',
},
});
// create some fields with useField
const { value } = useField('email');
function submit() {
// send stuff to api
}
return {
value,
meta,
submit,
};
},
};
</script>