@k8ordo/form

仕組み

@k8ordo/formがどう動いているかを説明します。使い方を覚えるのに必要な内容ではありませんが、なぜそう書くのかが分かると、迷ったときに判断しやすくなります。

このページの内容

1つのスキーマが3か所で働く

スキーマを使うのは、サーバーで入力欄の情報を作るときと、サーバーで送信を検証するときの2回です。ブラウザが受け取るのは、そこから作ったJSONだけです。

text
server   formFields(schema)       → { fields, arrays, rules, dropped }
           ↓ props (JSON)
client   useForm(fields, state)   → props for <form>, input for each field
           ↓ submit
server   parseForm(schema, data)  → typed data, or errors per field

そのため、zodはブラウザのバンドルに入りません。スキーマはzodでもzod/miniでも書けますが、ブラウザから読まないなら、どちらを選んでもバンドルの大きさは変わりません。

値はDOMが持つ

入力された値は、Reactのstateに写さずにDOMに置いたままにします。stateに持つのは、DOMでは表せない次の5つだけです。

  • ブラウザが失敗と判断した入力欄に、どの文言を出すか
  • サーバーが返したエラーのうち、まだ直していないものはどれか
  • 繰り返しの行のそれぞれを見分けるキー
  • DOMから読み直した、変更があるかどうかの1つの真偽値
  • 行の追加や削除を比べるための、もとの行数

値を写さないので、キーを押すたびに再描画されることはありません。また、ブラウザのリセットやReactがアクションのあとに行うリセットも、そのまま正しく働きます。

JavaScriptが無くても動く理由

入力欄の制約は、サーバーが描いたHTMLの属性に入っています。そのため、JavaScriptが届く前でもブラウザ自身が入力を確かめます。

JavaScriptが届くと、useFormはフォームにnoValidateを付け、同じ検証をzodの文言で行います。noValidateはHTMLには書かずに後から付けるので、JavaScriptが無い環境ではブラウザの検証が残ります。

送信に失敗したときは、送信した値が入力欄のdefaultValueとして描き直されます。JavaScriptが無い環境でも、入力した内容は失われません。

文言がブラウザとサーバーで食い違わない理由

formFieldsは、検証の種類ごとに、その検証だけが失敗する値をスキーマに渡し、返ってきたエラーの文言を取り出します。ブラウザで表示する文言は、zodがサーバーで出す文言そのものです。

requiredの決まり方

JSON Schemaのrequiredは「キーがある」という意味ですが、フォームはどの入力欄からも何かしらを送ります。そこでformFieldsは、その入力欄が空のときに送る値をスキーマが拒むかどうかで、requiredを付けるかを決めます。

空のときに送る値は、入力欄によって違います。テキストは""、チェックの無いチェックボックスはfalseです。数値やファイル、選択肢は何も送りません。parseFormも同じ値をスキーマに渡すので、ブラウザとサーバーの判断がそろいます。下の例では、空の文字列を受け付けるbioと、空欄を省けるageにはrequiredが付きません。

ts
z.object({ bio: z.string() });
// bio: { name: 'bio', type: 'text' }

z.object({ title: z.string().min(1) });
// title: { name: 'title', required: true, type: 'text', minLength: 1 }

z.object({ age: z.coerce.number().optional() });
// age: { name: 'age', type: 'number', step: 'any' }

ブラウザで確かめられない検証を知らせる

スキーマ全体に付けた.refine()や、フラグ付きの正規表現のように、HTMLの属性で表せない検証があります。formFieldsはこうした検証を黙って捨てずに、droppedに並べて返します。

本番環境以外では、console.warnでも一覧を出します。どの検証もサーバーでは必ず行われるので、ブラウザで確かめないだけです。

フォームで表せないスキーマはエラーにする

フォームが送る値はいつも文字列なので、z.number()やz.date()はどんな入力も受け付けない入力欄になります。z.recordやタプルのように、送信する名前が決まらない形もあります。formFieldsは、こうしたスキーマを受け取ると理由を添えてエラーを投げます。

入力した内容を黙って捨てたり、決して通らないフォームを作ったりするのは、間違いとして知らせるべきだと考えているからです。

k8ordo

Baselineに入った機能を、制限なく使うReactのライブラリ群。

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2