React Router
React Router の Framework Mode とインテグレーションしたログインフォームの例です。完全な例は React Router のサンプル を参照してください。
このガイドでは Conform の future API を使用します。これらの API は実験的なもので、マイナーバージョンで変更される可能性があります。詳しくはこちら
この例は app/routes/login.tsx のルートモジュールを想定しています。Route 型は React Router によって生成されます。
1import { useForm, parseSubmission, report } from '@conform-to/react/future';
2import { coerceFormValue } from '@conform-to/zod/v4/future';
3import { Form, redirect } from 'react-router';
4import { z } from 'zod';
5import type { Route } from './+types/login';
6
7const schema = coerceFormValue(
8 z.object({
9 email: z.string({ error: 'Email is required' }).email('Email is invalid'),
10 password: z.string({ error: 'Password is required' }),
11 remember: z.boolean().default(false),
12 }),
13);
14
15export async function action({ request }: Route.ActionArgs) {
16 const formData = await request.formData();
17 const submission = parseSubmission(formData);
18 const result = schema.safeParse(submission.payload);
19
20 if (!result.success) {
21 return {
22 result: report(submission, {
23 error: {
24 issues: result.error.issues,
25 },
26 }),
27 };
28 }
29
30 // ...
31
32 return redirect('/');
33}
34
35export default function Login({ actionData }: Route.ComponentProps) {
36 // クライアントでバリデーション・ロジックを再利用する
37 const { form, fields } = useForm(schema, {
38 // 前回の送信結果を同期
39 lastResult: actionData?.result,
40
41 // blurイベント発生時にフォームを検証する
42 shouldValidate: 'onBlur',
43 shouldRevalidate: 'onInput',
44 });
45
46 return (
47 <Form method="post" {...form.props}>
48 <div>
49 <label htmlFor={fields.email.id}>Email</label>
50 <input
51 id={fields.email.id}
52 type="email"
53 name={fields.email.name}
54 defaultValue={fields.email.defaultValue}
55 aria-invalid={!fields.email.valid || undefined}
56 aria-describedby={fields.email.ariaDescribedBy}
57 />
58 <div id={fields.email.errorId}>{fields.email.errors}</div>
59 </div>
60 <div>
61 <label htmlFor={fields.password.id}>Password</label>
62 <input
63 id={fields.password.id}
64 type="password"
65 name={fields.password.name}
66 defaultValue={fields.password.defaultValue}
67 aria-invalid={!fields.password.valid || undefined}
68 aria-describedby={fields.password.ariaDescribedBy}
69 />
70 <div id={fields.password.errorId}>{fields.password.errors}</div>
71 </div>
72 <div>
73 <label htmlFor={fields.remember.id}>Remember me</label>
74 <input
75 id={fields.remember.id}
76 type="checkbox"
77 name={fields.remember.name}
78 defaultChecked={fields.remember.defaultChecked}
79 aria-invalid={!fields.remember.valid || undefined}
80 aria-describedby={fields.remember.ariaDescribedBy}
81 />
82 </div>
83 <hr />
84 <button>Login</button>
85 </Form>
86 );
87}