-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathserver-side-validation.tsx
151 lines (133 loc) · 5.06 KB
/
server-side-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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// This code is live at https://react-zorm.vercel.app/server-side-validation
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { z } from "zod";
import { useZorm, parseForm, createCustomIssues } from "react-zorm";
import type { ActionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
/**
* Handle checkbox as boolean
*/
const booleanCheckbox = () =>
z
.string()
// Unchecked checkbox is just missing so it must be optional
.optional()
// Transform the value to boolean
.transform(Boolean);
/**
* The form schema
*/
const SignupSchema = z.object({
email: z.string().email(),
password: z.string().min(5),
terms: booleanCheckbox().refine((value) => value === true, {
message: "You must agree!",
}),
});
/**
* The form route
*/
export default function ZormFormExample() {
/**
* The form response or undefined when the form is not submitted yet
*/
const formResponse = useActionData<typeof action>();
const zo = useZorm("signup", SignupSchema, {
// Pass server issues to Zorm as custom issues. Zorm will handle them
// like any other Zod issues
customIssues: formResponse?.serverIssues,
});
const submitting = useNavigation().state === "submitting";
return (
<div>
<Form method="post" ref={zo.ref}>
<fieldset>
<legend>Signup</legend>
<div>
Email:
<input
name={zo.fields.email()}
type="email"
defaultValue="[email protected]"
/>
{zo.errors.email((err) => (
// This will render client-side errors as well as
// the server-side issues that where assigned to the
// "email" field
<Err>{err.message}</Err>
))}
</div>
<div>
Password:
<input
type="password"
name={zo.fields.password()}
defaultValue="hunter2"
></input>
{zo.errors.password((err) => (
<Err>{err.message}</Err>
))}
</div>
<div>
<input
type="checkbox"
name={zo.fields.terms()}
id={zo.fields.terms("id")}
defaultValue="1"
></input>
<label htmlFor={zo.fields.terms("id")}>
I agree to stuff
</label>
{zo.errors.terms((err) => (
<Err>{err.message}</Err>
))}
</div>
<button disabled={submitting}>
{submitting ? "Sending..." : "Signup!"}
</button>
{formResponse?.ok ? (
<div className="ok">User created!</div>
) : null}
</fieldset>
</Form>
<footer>
The [email protected] email is validated to be reserved on the
server. Just submit the form the see it in action. Checkout the
devtools network tab and the source of this{" "}
<a href="https://github.com/esamattis/react-zorm/blob/master/packages/remix-example/app/routes/server-side-validation.tsx">
here
</a>
.
</footer>
</div>
);
}
export async function action({ request }: ActionArgs) {
// Read the form data and parse it with Zorm's parseForm() helper
const form = await request.formData();
const data = parseForm(SignupSchema, form);
const issues = createCustomIssues(SignupSchema);
console.log("Validating...");
// Simulate slower database/network connection
await new Promise((r) => setTimeout(r, 1000));
// In reality you would make a real database check here or capture a
// constraint error from user insertion
if (data.email === "[email protected]") {
// Add an issue the email field. This generates a ZodCustomIssue
issues.email("Account already exists with " + data.email, {
anything: "Any extra params you want to pass to ZodCustomIssue",
});
}
// Respond with the issues if we have any
if (issues.hasIssues()) {
return json(
{ ok: false, serverIssues: issues.toArray() },
{ status: 400 },
);
}
console.log("Form ok. Saving...");
return json({ ok: true, serverIssues: [] });
}
function Err(props: { children: string }) {
return <div className="error">{props.children}</div>;
}