概要
Conform は、Web 標準に基づいて HTML フォームを段階的に強化し、 Remix や Next.js のようなサーバーフレームワークを完全にサポートする、型安全なフォームバリデーションライブラリです。
#特徴
- プログレッシブエンハンスメント・ファーストな API
- 型安全なフィールド推論
- きめ細やかなサブスクリプション
- 組み込みのアクセシビリティ・ヘルパー
- Zod による自動型強制
#The Gist
Conformは、クライアントからサーバーへのフォーム送信のライフサイクルを制御し、useForm()
フックを通じてフォームの状態を提供します。フォームのマークアップを制限せず、有効なHTMLフォームであればどのようなものでも動作します。フォームの値は、FormData Web APIを使用してDOMから取得され、イベント委譲によって同期されます。
1import { useForm } from '@conform-to/react';
2import { parseWithZod } from '@conform-to/zod';
3import { z } from 'zod';
4import { login } from './your-auth-library';
5import { useActionResult, redirect } from './your-server-framework';
6
7// フォームのスキーマを定義する
8const schema = z.object({
9 username: z.string(),
10 password: z.string(),
11});
12
13// Optional: サーバーアクションハンドラ
14export async function action({ request }) {
15 const formData = await request.formData();
16 const submission = parseWithZod(formData, { schema });
17
18 // ステータスが成功でない場合は、送信内容をクライアントに返送する
19 if (submission.status !== 'success') {
20 return submission.reply();
21 }
22
23 const session = await login(submission.value);
24
25 // ログインに失敗した場合は、追加のエラーメッセージとともに送信内容を送る
26 if (!session) {
27 return submission.reply({
28 formErrors: ['Incorrect username or password'],
29 });
30 }
31
32 return redirect('/dashboard');
33}
34
35// クライアントフォームコンポーネント
36export default function LoginForm() {
37 // サーバーアクションハンドラを定義している場合は、最後の送信結果を取得します。
38 // これはフレームワークによってはuseActionData()またはuseFormState()になります。
39 const lastResult = useActionResult();
40 const [form, fields] = useForm({
41 // 各フィールドをいつ検証するかを設定する
42 shouldValidate: 'onBlur',
43 // Optional: サーバー上で検証する場合のみ必要です。
44 lastResult,
45 // Optional: クライアント検証。提供されていない場合はサーバー検証にフォールバックします。
46 onValidate({ formData }) {
47 return parseWithZod(formData, { schema });
48 },
49 });
50
51 return (
52 <form method="post" id={form.id} onSubmit={form.onSubmit}>
53 <div>{form.errors}</div>
54 <div>
55 <label>Username</label>
56 <input type="text" name={fields.username.name} />
57 <div>{fields.username.errors}</div>
58 </div>
59 <div>
60 <label>Password</label>
61 <input type="password" name={fields.password.name} />
62 <div>{fields.password.errors}</div>
63 </div>
64 <button>Login</button>
65 </form>
66 );
67}