-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathconform.md
More file actions
126 lines (100 loc) · 3.7 KB
/
conform.md
File metadata and controls
126 lines (100 loc) · 3.7 KB
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
# Conform
Conform, created by Edmund Hung, is a type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix, React Router, Next.js.
Links:
- [Conform.guide](https://conform.guide)
- [`edmundhung/conform/examples/shadcn-ui`](https://github.com/edmundhung/conform/tree/main/examples/shadcn-ui): shadcn UI Integration
## Example: Remix
Here is a login form example integrating with [Remix](https://remix.run).
```tsx
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import type { ActionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string(),
remember: z.boolean().optional(),
});
export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== "success") {
return json(submission.reply());
}
// ...
}
export default function Login() {
// Last submission returned by the server
const lastResult = useActionData<typeof action>();
const [form, fields] = useForm({
// Sync the result of last submission
lastResult,
// Reuse the validation logic on the client
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
// Validate the form on blur event triggered
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
return (
<Form method="post" id={form.id} onSubmit={form.onSubmit} noValidate>
<div>
<label>Email</label>
<input
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
/>
<div>{fields.email.errors}</div>
</div>
<div>
<label>Password</label>
<input
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
/>
<div>{fields.password.errors}</div>
</div>
<label>
<div>
<span>Remember me</span>
<input
type="checkbox"
key={fields.remember.key}
name={fields.remember.name}
defaultChecked={fields.remember.initialValue === "on"}
/>
</div>
</label>
<hr />
<button type="submit">Login</button>
</Form>
);
}
```
### Tips
The default value might be out of sync if you reset the form from the action.
If the default value of the form comes from the loader and you are trying to reset the form on the action, there is a chance you will see the form reset to the previous default value. As Conform will reset the form the moment action data is updated while Remix is still revalidating the loader data. To fix this, you can wait for the state to be `idle` (e.g. `navigation.state` or `fetcher.state`) before passing the `lastResult` to Conform like this:
```tsx
export default function Example() {
const { defaultValue } = useLoaderData<typeof loader>();
const lastResult = useActionData<typeof action>();
const navigation = useNavigation();
const [form, fields] = useForm({
// If the default value comes from loader
defaultValue,
// Sync the result of last submission only when the state is idle
lastResult: navigation.state === "idle" ? lastResult : null,
// or, if you are using a fetcher:
// lastResult: fetcher.state === 'idle' ? lastResult : null,
// ...
});
// ...
}
```