formsの回で作ったフォームは、入力欄3つですでに60行ありました。実務のフォームは10欄を超えることがあります。そうなると自前では、こうなります。
useStateが10個- 検証のif文が10個
- エラーメッセージのstateが10個
- リセットで10回
setXxx('')
同じ形のコードが増えるだけなので、ここは道具に任せる場面です。
入れるもの
npm install --save-exact react-hook-form zod @hookform/resolvers| パッケージ | 役目 |
|---|---|
| react-hook-form | 入力値・検証・エラー・送信中をまとめて管理 |
| zod | 検証ルールを書く(そこから型も作れる) |
| @hookform/resolvers | 2つをつなぐ |
検証ルールを1か所に書く
まずzodで「正しい形」を定義します。
import { z } from 'zod'
const schema = z.object({
name: z.string().trim().min(1, '名前を入れてください').max(20, '20文字までにしてください'),
area: z.string().trim().max(20, '20文字までにしてください'),
emoji: z.string(),
})
type FormValues = z.infer<typeof schema>最後の1行が効きます。z.inferで、検証ルールから型が作られます。
// FormValues は { name: string; area: string; emoji: string } になる検証と型を別々に書かないので、片方だけ直してずれる事故が起きません。
フォームにつなぐ
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import type { Bird } from '../types.ts'
type Props = {
onAdd: (bird: Bird) => void
}
function AddBirdForm({ onAdd }: Props) {
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { name: '', area: '', emoji: '🐧' },
})
function onSubmit(values: FormValues) {
onAdd({ id: crypto.randomUUID(), ...values, area: values.area || 'ひみつ', rare: 1, memo: '' })
reset()
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" {...register('name')} />
{errors.name && <p className="text-red-600">{errors.name.message}</p>}
<input type="text" {...register('area')} />
{errors.area && <p className="text-red-600">{errors.area.message}</p>}
<button type="submit" disabled={isSubmitting}>ずかんに追加</button>
</form>
)
}自前版と比べて、消えたものを並べます。
| 自前で書いていたもの | どうなったか |
|---|---|
useState×4 | 消えた(useFormが持つ) |
valueとonChange | {...register('name')}の1つに |
検証のif文 | zodのスキーマへ |
| エラー用のstate | formState.errorsに |
送信後のsetXxx('')×3 | reset()の1回に |
入れる/入れないの線引き
| 状況 | 判断 |
|---|---|
| 入力が2〜3個・検証も単純 | 自前のuseStateで十分 |
| 検証ルールが増えてきた | 入れる |
| 同じ検証をサーバーでも使いたい | 入れる(zodのスキーマを共有する) |
| フィールドを動的に増減する(入力欄の追加ボタン) | 入れる |
| 入力欄が多くて重い | 入れる |
公式ドキュメントも見てみる
React Hook Form外部サイトGet Started | React Hook FormReact Hook Form公式のGet Started。register・handleSubmit・formStateの最小の形がまとまっています(英語)このレッスンのまとめ
- 自前フォームは入力欄の数だけコードが増える(state・検証・エラー・リセット)
- React Hook Formが入力値・検証・エラー・送信中をまとめて持つ
- zodで検証ルールを1か所に書き、
z.inferで型も作る(ずれない) - 型は書いている最中、zodは動いているとき——役割が違う
{...register('name')}は制御コンポーネントの4点セットを1行にしたもの- 入力のたびに再描画しないので、欄が多い画面でも重くなりにくい
- 入力2〜3個なら自前で十分。検証が増えた・サーバーと共有したいときに入れる
次はテストです。書いたものが壊れていないことを、機械に確かめてもらいます。