インテグレーション / React Router

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}