Zod for react-hook-form

Why zod for form validation?

In the past I've quite enjoyed the beauty of vest for it's nearness to unit testing. It didn't require a complex dsl, conditionals worked mostly as one would expect, and the error messages could be raised. Moving from yup before that was mostly because writing any complex validation required re-reading the docs and reading the validations also required re-reading the docs.

Zod is taking over the typed world. There's nothing wrong with vest, but we gain form types for free when defining a form schema which is one less thing to do. Additionally, zod is being used for other validation so consolidating on a single library simplifies the overall api surface area of our application for input, server, and client side validation.

Replacing vest with zod

I’ve been trying zod with trpc more and it might be the way, but it also wants a discriminated union where the type exists at the top level.

I think we can replace vest with zod, here’s why

  • parsing returns a typed object and can throw/filter out values that aren’t in the schema
  • although I dislike domain specific languages (dsl’s) where you write things as chained().functions(), zod’s is relatively lightweight and pretty close to ts on purpose. It’s not a full blown dsl like yup or joi.
  • normally dsl’s breakdown when there’s a conditional, but in zod it's not horrible. We can accomplish most of our conditionals in a refine.

let exampleSchema = z
.object({
test: z.string(),
otherValue: z.object({
name: z.string(),
}),
})
.refine(
(data) => {
if (data.otherValue.name === 'invalid') {
return false
}
return true
},
{
message: "Passwords don't match",
path: ['otherValue.name'], // nested path of error
}
)
.refine(
(data) => {
return false
},
{
message: 'another check',
path: ['test'], // path of error
}
)
try {
let x = exampleSchema.parse({
test: 'true',
otherValue: {
name: 'invalid',
},
})
console.log({ x })
} catch (error) {
console.log(error)
}