conformZodMessage
A set of custom messages to control the validation behavior. This is useful if you need async validation for one of the fields.
#Options
conformZodMessage.VALIDATION_SKIPPED
This message is used to indicate that the validation is skipped and Conform should use the previous result instead.
conformZodMessage.VALIDATION_UNDEFINED
This message is used to indicate that the validation is not defined and Conform should fallback to server validation.
#Example
Here is a signup form example which validates if the email is unique.
1import type { Intent } from '@conform-to/react';
2import { useForm } from '@conform-to/react';
3import { parseWithZod, conformZodMessage } from '@conform-to/zod';
4import { z } from 'zod';
5
6// Instead of sharing a schema, prepare a schema creator
7function createSchema(
8 // The `intent` will be provided by the `parseWithZod` helper
9 intent: Intent | null,
10 options?: {
11 isEmailUnique: (email: string) => Promise<boolean>;
12 },
13) {
14 return z
15 .object({
16 email: z
17 .string()
18 .email()
19 // Pipe the schema so it runs only if the email is valid
20 .pipe(
21 z.string().superRefine((email, ctx) => {
22 const isValidatingEmail =
23 intent === null ||
24 (intent.type === 'validate' && intent.payload.name === 'email');
25
26 if (!isValidatingEmail) {
27 ctx.addIssue({
28 code: 'custom',
29 message: conformZodMessage.VALIDATION_SKIPPED,
30 });
31 return;
32 }
33
34 if (typeof options?.isEmailUnique !== 'function') {
35 ctx.addIssue({
36 code: 'custom',
37 message: conformZodMessage.VALIDATION_UNDEFINED,
38 fatal: true,
39 });
40 return;
41 }
42
43 return options.isEmailUnique(email).then((isUnique) => {
44 if (!isUnique) {
45 ctx.addIssue({
46 code: 'custom',
47 message: 'Email is already used',
48 });
49 }
50 });
51 }),
52 ),
53 })
54 .and(
55 z
56 .object({
57 password: z.string({ required_error: 'Password is required' }),
58 confirmPassword: z.string({
59 required_error: 'Confirm password is required',
60 }),
61 })
62 .refine((data) => data.password === data.confirmPassword, {
63 message: 'Password does not match',
64 path: ['confirmPassword'],
65 }),
66 );
67}
68
69export async function action({ request }) {
70 const formData = await request.formData();
71 const submission = await parseWithZod(formData, {
72 schema: (intent) =>
73 // create the zod schema based on the intent
74 createSchema(intent, {
75 isEmailUnique(email) {
76 // query from your database
77 },
78 }),
79 async: true,
80 });
81
82 if (submission.status !== 'success') {
83 return submission.reply();
84 }
85
86 // ...
87}
88
89export default function Signup() {
90 const lastResult = useActionData();
91 const [form, fields] = useForm({
92 lastResult,
93 onValidate({ formData }) {
94 return parseWithZod(formData, {
95 // Create the schema without `isEmailUnique` defined
96 schema: (intent) => createSchema(intent),
97 });
98 },
99 });
100
101 // ...
102}