Appearance
Validators
Validators are used to validate the input of a form or any input that can be validated using a schema.
You can use any VeeValidate supported validators for integration such as Yup and Valibot. Zod is also supported and works well at backend but have issues with frontend integration as Zod's .superRefine() method is not working well with VeeValidate. This is by the design of Zod.
Available Validators
We have the following installed:
- Zod
- Valibot
- Yup
Validator Folder Structure and Naming Convention
All validators must be placed in the validators folder and a validator file must be suffixed with .validator.ts If there is a conflict with the name of the validator, you can add the name of the module before the .validator.ts suffix.
text
validators/
user.zod.validator.ts
user.yup.validator.ts
employee.validator.ts
company.validator.tsFor example, if you have a validator for the user module, you can name it user.validator.ts but decided to use Valibot, you can name it user.v.validator.ts.
Here is a table of the naming convention:
| Validator | Suffix | Example |
|---|---|---|
| Zod | .zod.validator.ts | user.zod.validator.ts |
| Valibot (since it has long name) | .v.validator.ts | user.v.validator.ts |
| Yup | .yup.validator.ts | user.yup.validator.ts |
Valibot
Using valivot is a bit strange, you might notice that the schema you created was correct but was not validating correctly or works as intended. This is because Valibot is a bit different from Zod and Yup.
ts
const schema = v.pipe(
v.object({
project_node_id: v.optional(v.pipe(
v.string(),
v.nonEmpty('Project is required')
), ''),
task_node_id: v.optional(v.pipe(
v.string(),
v.nonEmpty('Task is required')
), ''),
from_time: v.optional(v.pipe(
v.string(),
v.nonEmpty('Start Time is required'),
v.check(val => dayjs(val).isValid(), 'Invalid datetime format')
), ''),
to_time: v.optional(v.pipe(
v.string(),
v.nonEmpty('End Time is required'),
v.check(val => dayjs(val).isValid(), 'Invalid datetime format')
), ''),
}),
v.rawCheck((action) => {
const { dataset, addIssue } = action;
if (dataset.typed) {
const input = dataset.value;
// Convert to dayjs object so we can use the comparison functions of dayjs
const fromTime = dayjs(input.from_time);
const toTime = dayjs(input.to_time);
if (toTime.isBefore(fromTime)) {
// Tell which field to show the error message. This is important.
// We are forwarding the error message on two fields, namely `from_time` and `to_time`.
addIssue({
message: 'Start Time should be earlier than End Time',
path: [
{
input,
key: 'from_time',
origin: 'value',
type: 'object',
value: input.from_time,
},
],
});
addIssue({
message: 'End Time should be later than Start Time',
path: [
{
input,
key: 'to_time',
origin: 'value',
type: 'object',
value: input.to_time,
},
],
});
}
})
);In this example, it is a validation schema that validates the field normally as we like, we want to run all the validators. The problem in Valibot is that when a missing field is found, it will stop the validation and will not run the other validators. So a solution was to use .optional() and add default value to the field that will go through the validation.
Consider the following example:
ts
v.object({
project_node_id: v.optional(v.pipe(
v.string(),
v.nonEmpty('Project is required')
), ''),
});Here, we marked the project_node_id as optional and added a default value of ''. This will pass on .string(), and will fail on .nonEmpty() if the field is empty. This is what we want.
On a scenario when the user clicks the button without entering any value, the value will be undefined. If we didn't do .optional(), it will initially have the following value:
js
{
project_node_id: undefined;
}This will cause the validation to stop at .string() and return an error Invalid type, expected string received undefined and will not run any .rawCheck or refinements which is not we want, so we added .optional() and a default value of '' to pass the .string() validation same logic goes to other fields.
Notes
- VeeValidate has it's own validator, it can also be used.