-
Notifications
You must be signed in to change notification settings - Fork 85
/
async-validation.tsx
120 lines (111 loc) · 3.02 KB
/
async-validation.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {
type Intent,
getFormProps,
getInputProps,
useForm,
} from '@conform-to/react';
import { conformZodMessage, parseWithZod } from '@conform-to/zod';
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { Form, useActionData, useLoaderData } from '@remix-run/react';
import { z } from 'zod';
import { Playground, Field } from '~/components';
function createSchema(
intent: Intent | null,
constraints: {
isEmailUnique?: (email: string) => Promise<boolean>;
} = {},
) {
return z.object({
email: z
.string({ required_error: 'Email is required' })
.email({ message: 'Email is invalid' })
// Pipe another schema so it runs only if it is a valid email
.pipe(
z.string().superRefine((email, ctx) => {
if (
intent &&
(intent.type !== 'validate' || intent.payload.name !== 'email')
) {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_SKIPPED,
});
return;
}
if (typeof constraints.isEmailUnique !== 'function') {
ctx.addIssue({
code: 'custom',
message: conformZodMessage.VALIDATION_UNDEFINED,
fatal: true,
});
return;
}
return constraints.isEmailUnique(email).then((isUnique) => {
if (!isUnique) {
ctx.addIssue({
code: 'custom',
message: 'Email is already used',
});
}
});
}),
),
title: z
.string({ required_error: 'Title is required' })
.max(20, 'Title is too long'),
});
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
return {
noClientValidate: url.searchParams.get('noClientValidate') === 'yes',
};
}
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = await parseWithZod(formData, {
schema: (intent) =>
createSchema(intent, {
isEmailUnique(email) {
return new Promise((resolve) => {
setTimeout(() => {
// Only `hey@conform.guide` is unique
resolve(email === 'hey@conform.guide');
}, Math.random() * 500);
});
},
}),
async: true,
});
return json(submission.reply());
}
export default function EmployeeForm() {
const { noClientValidate } = useLoaderData<typeof loader>();
const lastResult = useActionData<typeof action>();
const [form, fields] = useForm({
lastResult,
shouldRevalidate: 'onInput',
onValidate: !noClientValidate
? ({ formData }) =>
parseWithZod(formData, {
schema: (intent) => createSchema(intent),
})
: undefined,
});
return (
<Form method="post" {...getFormProps(form)}>
<Playground title="Employee Form" result={lastResult}>
<Field label="Email" meta={fields.email}>
<input
{...getInputProps(fields.email, { type: 'email' })}
autoComplete="off"
/>
</Field>
<Field label="Title" meta={fields.title}>
<input {...getInputProps(fields.title, { type: 'text' })} />
</Field>
</Playground>
</Form>
);
}