reactjs - Hur kan jag använda zod för att villkorsvis validera ett formulär med nästlade fält?
Jag försöker villkorslöst validera ett react-hook-form som ser ut så här med zod:
Funktionerna för valideringen är:
- Användaren måste markera minst en kontaktmetod, annars får de ett fel
- De måste sedan ange värdet för den kontaktmetod de har kryssat i
- Värdena förfinas och omvandlas sedan (t.ex. beskärs, analyseras till rätt format etc.)
Mitt schema ser ut så här:
const schema = z
.object({
// other fields...
contactMethods: z
.array(z.enum(["email", "phone"]), {
invalid_type_error: "At least one contact method is required",
})
.min(1, "At least one contact method is required"),
email: z
.string({ invalid_type_error: "Enter an email1" })
.trim()
.email("Enter a valid email"),
phone: z
.string({ invalid_type_error: "Enter a phone number" })
.trim()
.transform(val => val.replaceAll(" ", "")) // spaces confuse validator function below
.refine(val => validator.isMobilePhone(val), "Enter a valid phone number") // check value is parsable as phone number
.transform(val => parsePhoneNumber(val, "GB")?.number) // parse into +44 e.164 format if not already
})
.refine(vals => vals.contactMethods.includes("email") ? vals.email : true, { message: "Enter an email", path: ["email"] })
.refine(vals => vals.contactMethods.includes("phone") ? vals.phone : true, { message: "Enter a phone number", path: ["phone"] })
Jag försöker anpassa lösningen i den här stängda Zod-frågan.
Detta gör inte jobbet eftersom:
- Formuläret har några andra fält (utelämnade här för tydlighetens skull), och jag behöver se till att de passerar validering innan
.refine()-blocken längre ner aktiveras. Just nu kan användaren rätta det de tror är alla fel, och sedan se fler när de är klara. - Felmeddelanden för de nästlade fälten hänger kvar när jag har börjat skriva i ett av dem, även om jag avmarkerar den kontaktmetoden
Jag vet att zod stöder union ELLER-typer, vilket verkar vara användbart här i princip, men jag har svårt att förstå hur jag ska anpassa koden till min situation.
Öppen för förslag på hur man på ett mer generellt sätt kan förenkla denna kod.
Svar #1
Ett sätt att göra detta skulle vara att dela upp schemat i tre delar. Den första delen kontrollerar att det finns minst en kontaktmetod, men den bryr sig inte alls om data om email eller phone.
Detta korsas sedan med två andra scheman. Ett för att hantera email och ett för att hantera phone. Var och en av dessa scheman är en union av de två möjligheterna för respektive:
telefon/e-postfinns inte i kontaktmetoder.telefon/e-postfinns i kontaktmetoder, ochtelefon/e-postär giltigt.
Hela saken kommer samman i ett enda schema.
const contactMethods = z.enum(['email', 'phone'])
const schema = z
.object({
// other fields...
contactMethods: z
.array(contactMethods, {
invalid_type_error: 'At least one contact method is required',
})
.min(1, 'At least one contact method is required'),
})
.and(
z.union([
z.object({
contactMethods: z
.array(contactMethods)
.refine((val) => val.includes(contactMethods.Enum.email)),
email: z
.string({ invalid_type_error: 'Enter an email1' })
.trim()
.email('Enter a valid email'),
}),
z.object({
contactMethods: z
.array(contactMethods)
.refine((val) => !val.includes(contactMethods.Enum.email)),
}),
]),
)
.and(
z.union([
z.object({
contactMethods: z
.array(contactMethods)
.refine((val) => val.includes(contactMethods.Enum.phone)),
phone: z
.string({ invalid_type_error: 'Enter a phone number' })
.trim()
.transform((val) => val.replaceAll(' ', '')) // spaces confuse validator function below
.refine((val) => validator.isMobilePhone(val), {
message: 'Enter a valid phone number',
}) // check value is parsable as phone number
.transform((val) => parsePhoneNumber(val, 'GB')?.number), // parse into +44 e.164 format if not already
}),
z.object({
contactMethods: z
.array(contactMethods)
.refine((val) => !val.includes(contactMethods.Enum.phone)),
}),
]),
)
