複数の入力欄にまたがる検証
パスワードの確認や「2つ以上選ぶ」のように、1つの入力欄だけでは決まらない検証があります。こうした検証はHTMLの属性で表せず、関数である.refine()はブラウザへ送れません。そこで@k8ordo/formでは、これをルールとしてスキーマの横に宣言します。
このページの内容
ルールを宣言する
defineFormに、スキーマとルールの配列を渡します。
schema.tsexport const signup = defineForm(
z.object({
password: z.string().min(8),
confirm: z.string(),
}),
[sameAs('confirm', 'password', 'The passwords do not match')],
);できあがった定義は、スキーマの代わりにformFieldsとparseFormへ渡します。ルールはただのデータなので、入力欄の情報と一緒にブラウザへ届きます。
const signupFields = formFields(signup);
const parsed = parseForm(signup, formData);ルールに書く入力欄のパスは、スキーマから型で確かめられます。存在しない欄の名前を書くと、型エラーになります。
用意されているルール
いま使えるルールは3つです。
sameAs(field, other, message):fieldの値がotherの値と同じであることを求めます。パスワードの確認に使います。minChecked(field, min, message):同じ名前のチェックボックスのうち、min個以上にチェックが入っていることを求めます。requiredWhen(field, when, equals, message):whenの値がequalsのときだけ、fieldを必須にします。
schema.tsdefineForm(schema, [
sameAs('confirm', 'password', 'The passwords do not match'),
minChecked('topics', 2, 'Pick at least two'),
requiredWhen('reason', 'status', 'rejected', 'Give a reason'),
]);ルールの動きを試す
登壇の審査フォームです。「却下」のときだけ理由が必須になり、観点は2つ以上選ぶ必要があります。
試してみる
- 観点を2つ選んでから、判定で「却下」を選び、理由を空のまま「送信」を押してください。理由の入力欄にエラーが出ます。
- 判定を「承認」に戻すと、理由のエラーは消えます。
- 観点を1つだけにして送信すると、2つ以上選ぶよう求めるエラーが出ます。
ブラウザとサーバーで同じ判定をする
ルールは、ブラウザでもサーバーでも同じ処理で判定します。ブラウザは入力中のフォームを、サーバーは送信された値を確かめます。判定の処理が1つしかないので、両者の結果が食い違うことはありません。
ブラウザでは、ルールを満たさない入力欄にsetCustomValidityでエラーを付けます。そのため組み込みの検証と見分けがつかず、:user-invalidの擬似クラスも当たり、文言もほかのエラーと同じerrorに届きます。
1つの入力欄が複数のルールを満たさないときは、先に宣言したルールの文言を表示します。
リクエストの言語で文言を出す
ルールの文言には、文字列の代わりに関数も渡せます。関数はルールを宣言したときではなく、エラーを報告するときに呼ばれます。
schema.tsexport const signup = defineForm(schema, [
sameAs('confirm', 'password', m.signup.mismatch),
minChecked('topics', 2, () => m.signup.pickAtLeast(2)),
]);ブラウザはサーバーから送られた関数を呼べないので、formFieldsは入力欄の情報を作る時点で文言を確定させます。言語に合わせるなら、formFieldsを描画の中で呼んでください。
ルールで書けない検証
3つのルールで書けない検証は、これまでどおり.refine()で書きます。ただし、.refine()はサーバーでしか動きません。
schema.tsz.object({
start: z.iso.date(),
end: z.iso.date(),
}).refine((value) => value.start <= value.end, {
message: 'The end comes before the start',
});スキーマ全体に付けた.refine()は、ブラウザでは確かめない検証としてdroppedに載ります。pathを指定しなければ、エラーはform.formErrorに届きます。
落とし穴
1つの入力欄や入れ子のオブジェクト、配列の行に付けた.refine()は、まだdroppedに載りません。ブラウザでは何も知らせずに素通りし、送信してから初めてサーバーでエラーになります。