はじめに
React Hook FormやFormikでフォームを組むとき、「スキーマは別途Zodやyupで書いて、resolverで繋いで、型はジェネリクスで手動指定する」という手順を毎回踏んでいないでしょうか。スキーマとフォームの型が二重管理になりがちで、片方を直してもう片方を直し忘れる、というミスは意外とよく起こります。
Formischは、Valibotの作者であるFabian Hiller氏が手がける、スキーマファーストのフォームライブラリです。resolverという概念そのものがなく、Valibotで書いたスキーマがそのままフォームの検証ルールと型定義の両方になります。React・Solid・Vue・Svelte・Angular・Qwik・Preact・React Nativeという幅広いフレームワークに対応しながら、バンドルサイズは2.5kBからという軽さも特徴です。
とはいえ、説明よりも動かした方が早いと思います。Valibotのスキーマ1つでフォームの検証と型が揃う様子をこの場で確認できるので、先に触ってみたい方はこちらからどうぞ。
Formischとは
Formischは、JSフレームワーク向けのヘッドレスなフォーム状態管理ライブラリです。「ヘッドレス」とはUIコンポーネントを提供せず、状態管理とバリデーションのロジックだけを提供するという意味で、見た目は自分のスタイルやコンポーネントライブラリで自由に組み立てられます。
最大の特徴は、Valibotのスキーマがそのまま「フォームの検証ルール」であり「TypeScriptの型」でもある点です。react-hook-formのzodResolverのような変換層を挟まず、useForm({ schema })に渡すだけでフォーム全体の型がスキーマから自動推論されます。
主な特徴
- スキーマファースト - Valibotのスキーマがそのまま検証ルールと型定義になり、resolverによる変換が不要
- フルスタックな型安全性 - フィールドの値・エラー・パスまで、Valibotの型推論がすべてに及ぶ
- 軽量なバンドルサイズ - モジュール分割設計により2.5kBから利用でき、必要な機能だけを読み込める
- マルチフレームワーク対応 - React、Solid、Vue、Svelte、Angular、Qwik、Preact、React Nativeに対応
- きめ細かいDOM更新 - フィールド単位で再レンダリングを最適化し、大きなフォームでも高速に動作する
インストール
Reactプロジェクトの場合、@formisch/reactとValibotをインストールします。
# npm
npm install @formisch/react valibot
# yarn
yarn add @formisch/react valibot
# pnpm
pnpm add @formisch/react valibot
Solid・Vue・Svelte・Angular・Qwik・Preact・React Native向けにも、それぞれ@formisch/solid・@formisch/vue・@formisch/svelteなどのパッケージが用意されています。
Formischのサンプルを動かす
このサンプルでは、Valibotのv.object()でログインフォームのスキーマを定義し、useFormにそのまま渡しています。<Form>が送信を、<Field>が各入力欄の状態・値・エラーをそれぞれ管理し、field.propsをそのまま<input>に展開するだけで双方向バインディングが完成します。
要点だけを抜き出すと、次のようになります。
import * as v from 'valibot'
import { useForm, Form, Field } from '@formisch/react'
const LoginSchema = v.object({
email: v.pipe(v.string(), v.email('メール形式が不正です')),
password: v.pipe(v.string(), v.minLength(8, '8文字以上必要です')),
})
const loginForm = useForm({ schema: LoginSchema })
<Form of={loginForm} onSubmit={(output) => console.log(output)}>
<Field of={loginForm} path={['email']}>
{(field) => <input {...field.props} value={field.input} />}
</Field>
</Form>
実際に動かせるものが下です。メールアドレスを空にしたりパスワードを7文字以下にするとv.email()・v.minLength(8)のエラーがその場で表示され、両方満たすとサブミットが通ってコンソールに入力値が出力されます。
初期状態ではメールアドレス・パスワードともに空欄なので、まずは値を入れずに「ログイン」を押してエラー表示を確認してみてください。次にメールアドレスにtestのような不正な形式を入力すると、v.email()のエラーがその場(validate: 'change')で切り替わります。フィールドのerrors配列とフォーム全体のisSubmittedを組み合わせることで、「編集済みか送信済みのフィールドだけエラーを出す」という一般的なUXパターンが実現できます。
基本的な使い方
最小構成では、スキーマを定義してuseFormに渡し、<Form>と<Field>でラップするだけです。
import * as v from 'valibot'
import { useForm, Form, Field } from '@formisch/react'
const ProfileSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, '名前は必須です')),
})
function ProfileForm() {
const profileForm = useForm({ schema: ProfileSchema })
return (
<Form of={profileForm} onSubmit={(output) => console.log(output)}>
<Field of={profileForm} path={['name']}>
{(field) => (
<>
<input {...field.props} value={field.input} />
{field.errors && <span>{field.errors[0]}</span>}
</>
)}
</Field>
<button type="submit">送信</button>
</Form>
)
}
pathには配列でフィールドの位置を指定します。フラットなnameだけでなく、後述するネストしたオブジェクトや配列でも同じpathの考え方で扱えるのがFormischの一貫した設計です。
実践的なユースケース
ネストしたオブジェクトの入力
住所や勤務先情報のように、フォームの中に構造化されたデータを持つケースは珍しくありません。Formischではv.object()を入れ子にするだけでスキーマ側にネスト構造を表現でき、<Field>のpathにはそのままキーの階層を配列で渡します。path={['address', 'city']}のように書くだけで、深い階層でも型安全にアクセスできます。
郵便番号に数字4桁だけなどv.regex()のパターンに合わない値を入れると、その場でエラーが表示されます。ネストが深くなってもpathに階層をそのまま並べるだけでよく、フィールドごとにpropsをバケツリレーする必要がない点がFormischらしいところです。
動的なリストの入力(FieldArray)
TODOリストや明細行のように、項目数が可変のフォームには<FieldArray>を使います。fieldArray.itemsをArray.prototype.map()で回して<Field>を並べ、insert・remove・swap・moveといった操作関数で配列そのものを更新します。
import { insert, remove } from '@formisch/react'
<FieldArray of={todoForm} path={['todos']}>
{(fieldArray) => (
<>
{fieldArray.items.map((_, index) => (
<Field of={todoForm} path={['todos', index, 'label']}>
{(field) => <input {...field.props} value={field.input} />}
</Field>
))}
<button onClick={() => insert(todoForm, {
path: ['todos'],
initialInput: { label: '' },
})}>追加</button>
</>
)}
</FieldArray>
実際に項目を追加・削除できるサンプルは以下の通りです。「追加」で新しい入力欄が増え、各行の「削除」で該当行だけが消えます。ラベルを空にしたまま「保存」を押すと、その行だけにエラーが表示されます。
FieldArrayのitemsはキー管理用のIDリストであり、実データは同じpathを経由して<Field>側から読み書きします。insert・remove以外にもmove(並べ替え)・swap(2項目の入れ替え)・replace(丸ごと置換)が用意されているため、ドラッグ&ドロップでの並べ替えや複製といった操作も同じ関数群だけで実装できます。
まとめ
Formischは、Valibotのスキーマをフォームの検証ルールと型定義の両方として使い回せる、スキーマファーストなフォームライブラリです。resolverという変換層を挟まない分だけ設定がシンプルになり、pathという共通の考え方で単一フィールドもネストしたオブジェクトも配列も同じように扱えます。2.5kBからという軽量さと、React・Solid・Vue・Svelteをまたいだ共通API設計も相まって、react-hook-formやFormikに次ぐ選択肢として押さえておく価値のあるライブラリです。まずは手元の小さなフォームをFormischで書き換えて、スキーマと型が一体化する感覚を試してみてください。
