はじめに / 概要

概要

Conform は、Web 標準に基づいて HTML フォームを段階的に強化し、 RemixNext.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}