はじめに
ReactでForm実装といえば React Hook Form !!
でもvalidationがちょっと弱い・・・。
validationといえば Zod !!
どっちも実装したことがない・・・。
やってみます!!!
もくじ
- プロジェクト用意
- React Hook Formをいろいろさわる
- Zodに入門してみる
- React Hook Form + Zod で簡単フォーム作成&バリデーション
プロジェクト用意
vite + Reactプロジェクト用意
pnpm create vite zod-hello-world --template react-ts
プロジェクトを作成します。
cd zod-hello-world
移動して・・・。
pnpm install
関連パッケージインストールします。
pnpm run dev
はろーわします。
良い感じですね!
viteやpnpmって何?って方は以下の記事もぜひ読んでみてください!(宣伝)
React Hook Form をいろいろさわる
では、まずは React Hook Form でFormを実装してみます!!
React Hook Form ライブラリ用意
pnpm add react-hook-form
ライブラリをインストールします。
Packages: +1
+
Progress: resolved 229, reused 191, downloaded 0, added 1, done
dependencies:
+ react-hook-form 7.53.1
Done in 1.5s
無事おわりました。
React Hook Form をちょろっと触ってみる
以下のサンプルコードをいじって、React Hook Formをふんわり理解します。
コードをコピペして問題なく動いたら、コードを確認してライブラリの仕様を確認します。
コンポーネント準備
zod-hello-world
├── node_modules
├── public
├── src
│ ├── assets
+ │ └── components // 新規作成
+ │ └── VanillaForm.tsx // 新規作成。React Hook Form を使ってみる
├── App.css
+ ├── App.tsx // VanillaForm をレンダリングするよう修正
├── index.css
├── index.html
├── main.tsx
├── package.json
├── pnpm-lock.yaml
├── README.md
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vite-env.d.ts
React Hook Form を使ってみるコンポーネントを1つ作成します。
また、そのコンポーネントをレンダリングするようApp.tsxを修正します。
src/components/VanillaForm.tsx 作成
get startedページのコンポーネントを翻訳しつつコピペします。
import { useForm, SubmitHandler } from "react-hook-form";
type Inputs = {
example: string,
exampleRequired: string,
};
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
console.log(watch("example")) // 入力値を監視する - 入力項目の名前を渡すことで監視可能
return (
/* "handleSubmit"はonSubmitを呼び出す前に入力値のバリデーションを行います */
<form onSubmit={handleSubmit(onSubmit)}>
{/* registerフォーム関数を呼び出して入力フィールドをhookに登録します */}
<input defaultValue="test" {...register("example")} />
{/* required属性やその他の標準HTMLバリデーションルールを含めることができます */}
<input {...register("exampleRequired", { required: true })} />
{/* フィールドのバリデーションが失敗した場合にエラーを表示します */}
{errors.exampleRequired && <span>この項目は必須です</span>}
<input type="submit" />
</form>
);
}
src/App.tsx 修正
差分多すぎるので修正後のソースを記載します。
レンダリングしてるだけですね。
import VanillaForm from "./components/VanillaForm"
function App() {
return (
<>
<VanillaForm />
</>
)
}
export default App
localhostで表示を確認
寂しすぎますが、良い感じですね。
submitするとしかられました。嬉しいですね。
サンプルコードを読み解く
コードを再掲します。上から見ていきます
import { useForm, SubmitHandler } from "react-hook-form";
type Inputs = {
example: string,
exampleRequired: string,
};
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
console.log(watch("example")) // 入力値を監視する - 入力項目の名前を渡すことで監視可能
return (
/* "handleSubmit"はonSubmitを呼び出す前に入力値のバリデーションを行います */
<form onSubmit={handleSubmit(onSubmit)}>
{/* registerフォーム関数を呼び出して入力フィールドをhookに登録します */}
<input defaultValue="test" {...register("example")} />
{/* required属性やその他の標準HTMLバリデーションルールを含めることができます */}
<input {...register("exampleRequired", { required: true })} />
{/* フィールドのバリデーションが失敗した場合にエラーを表示します */}
{errors.exampleRequired && <span>この項目は必須です</span>}
<input type="submit" />
</form>
);
}
見ていきます
// L1~
import { useForm, SubmitHandler } from "react-hook-form";
ライブラリからいろいろimportしてますね。
これらをどう使うかはあとで確認します。
// L3~
type Inputs = {
example: string,
exampleRequired: string,
};
Form要素のTypeを定義しています。型最高!!!
// L8~
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
importしたuseFormを実行しています。
ジェネリクスにInput型を渡してますね。
色々もらってるものがどんなものか確認していきましょう。
// L12~
console.log(watch("example")) // 入力値を監視する - 入力項目の名前を渡すことで監視可能
useForm
から受け取った watch
を使用しています。
コメントを信じて適当に値を入力してみます。
watchできてますね!引数なしだとForm全体をwatchするようです。なるほど。
// L16~
return (
/* "handleSubmit"はonSubmitを呼び出す前に入力値のバリデーションを行います */
<form onSubmit={handleSubmit(onSubmit)}>
続くformタグでは、useForm
から受け取った handleSubmit
を使用しています。
handleSubmit
を使用することで、バリデーションをonSubmit
前に行ってくれるらしいです。
関数の引数を確認すると、第二引数に onInvalid
を指定できるようです。
handleSubmit
はsubmit前にvalidationしてくれて、
その結果に応じて第一引数の関数 or 第二引数の関数を実行してくれます。便利!
コードを修正して確認します。
- import { useForm, SubmitHandler } from "react-hook-form";
+ import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";
type Inputs = {
example: string,
exampleRequired: string,
};
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
- const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
+ const onSubmit: SubmitHandler<Inputs> = data => console.log('submit', data);
+ const onInvalid: SubmitErrorHandler<Inputs> = data => console.log('invalid', data);
console.log(watch("example")) // 入力値を監視する - 入力項目の名前を渡すことで監視可能
return (
/* "handleSubmit"はonSubmitを呼び出す前に入力値のバリデーションを行います */
- <form onSubmit={handleSubmit(onSubmit)}>
+ <form onSubmit={handleSubmit(onSubmit, onInvalid)}>
invalid時の関数の型をimportし、handleSubmit
関数に渡します。
良い感じですね!
Valid時は関数内でFormが参照でき、
Invalid時は関数内でエラー情報が参照できてそうです
次はinputタグを作成しているところを見て見ます。
// L17~
{/* registerフォーム関数を呼び出して入力フィールドをhookに登録します */}
<input defaultValue="test" {...register("example")} />
register関数の使い方がなんか奇妙ですね。
これを紐解くために、そもそもregister関数が何を返すか確認します。
console.log(watch("example")) // 入力値を監視する - 入力項目の名前を渡すことで監視可能
+ console.log(register("example"))
return (
name
や onchange
が詰まっているオブジェクトが返却されるようです。
inputに渡したいpropsがオブジェクトになっているにように見えませんか?
豆知識なのですが、Reactのpropsはこのような渡し方ができます
const props = { hoge: 'aaa', fuga: 'bbb' }
<Component hoge={props.hoge} fuga={props.huga} />
// ↓↓↓
<Component {...props} />
今回のケースに置き換えるとこうですね。
const props = register("example")
// = {name: 'example', onChange: ƒ, onBlur: ƒ, ref: ƒ}
<input name={props.name} onChange={props.onChange} onBlur={props.onBlur} ref={props.ref} />
// ↓↓↓
<input {...props} />
つまり、このコードはinputタグをexampleに対応させるコードなんですね。
// L17~
{/* registerフォーム関数を呼び出して入力フィールドをhookに登録します */}
<input defaultValue="test" {...register("example")} />
※参考
最後に、エラーの表示方法を確認します。
// L9~
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
// L22~
{/* フィールドのバリデーションが失敗した場合にエラーを表示します */}
{errors.exampleRequired && <span>この項目は必須です</span>}
useFrom
で返却されたerrors
にエラー情報が入っていて、そこで分岐する感じですね。
errorsには型情報に応じた各メンバーが定義されていそうです。
?がついているので、エラー時のみtruthyになりそうですね。
エラー情報も全部hooksが管理してくれているのは直感的で便利そう!
なんとなくReact Hook Form の雰囲気は感じ取れたので、次はZodに入門してみます。
Zodに入門してみる
Zod ライブラリ用意
addしていきますー
pnpm add zod
Packages: +1
+
Progress: resolved 230, reused 192, downloaded 0, added 1, done
dependencies:
+ zod 3.23.8
Done in 1.5s
おっけーですね。
また、ローカルでさくっとtsファイルを実行するために、tsx というライブラリも追加します。
pnpm add -D tsx
Packages: +5
+++++
Progress: resolved 259, reused 198, downloaded 0, added 5, done
devDependencies:
+ tsx 4.19.1
Done in 4.2s
tsxってなに?という方は以下の記事がおすすめです!めっちゃわかりやすい
では、以下のサンプルコードをいじって、Zodをふんわり理解します。
今回はReact Hook Form とつなげるので、以下のようなFormを作ることをゴールにおきます。
名前:必須。
年齢:必須。20歳以上。80歳以下。
メールアドレス:必須。メアド形式必須。
パスワード:必須。8文字以上で英数字のみで構成されていること。
確認用パスワード:必須。パスワードと等しいこと。
やっていきましょう!
Zod 入門
zod-hello-world
├── node_modules
├── public
├── src
│ ├── assets
│ └── components
│ └── VanillaForm.tsx
├── App.css
├── App.tsx
+ ├── hello_zod.ts // 新規作成。zodをつかってみる
├── index.css
├── index.html
├── main.tsx
├── package.json
├── pnpm-lock.yaml
├── README.md
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vite-env.d.ts
Zod 要素の定義
zodでは以下のようなメソッドでプロパティの型を定義します。
import { z } from "zod";
// 名前フィールド
const name = z.string();
// 年齢フィールド(20-80歳)
const age = z.number();
// メールアドレスフィールド
const email = z.string();
// パスワードフィールド(8文字以上、英数字のみ)
const password = z.string();
// 確認用パスワードフィールド
const confirmPassword = z.string();
プリミティブな型やundefined、Nullなどもサポートされています。
ドキュメントより引用。
ただ、これらのフィールドを個々の変数で扱うのは微妙なので、オブジェクト化します。
Zod オブジェクトの定義
z.object()で引数にオブジェクトを与えてあげるだけでオブジェクトを定義できます!
import { z } from "zod";
// 名前フィールド
const name = z.string();
// 年齢フィールド(20-80歳)
const age = z.number();
// メールアドレスフィールド
const email = z.string();
// パスワードフィールド(8文字以上、英数字のみ)
const password = z.string();
// 確認用パスワードフィールド
const confirmPassword = z.string();
const user = z.object({
name,
age,
email,
password,
confirmPassword,
});
ではFormで扱いたいオブジェクトを定義できたので、validationしていきます!
Zod parse、safeParse
Zodでは、parse
, safeParse
というメソッドでvalidationをおこなうことができます。
parse
はvalidation失敗時にErrorをthrowするメソッドです。
safeParse
はvalidation失敗時にErrorをthrowせず、結果を返却するメソッドです。
今回は使いやすいsafeParseを使っていきます。
import { z } from "zod";
// 名前フィールド
const name = z.string();
// 年齢フィールド(20-80歳)
const age = z.number();
// メールアドレスフィールド
const email = z.string();
// パスワードフィールド(8文字以上、英数字のみ)
const password = z.string();
// 確認用パスワードフィールド
const confirmPassword = z.string();
const user = z.object({
name,
age,
email,
password,
confirmPassword,
});
+ console.log(user.safeParse({}))
舐めすぎてますが、なにもないオブジェクトをUserスキーマでParseしてみます
pnpm tsx .\src\hello_zod.ts
結果が出ます。
{ success: false, error: [Getter] }
parseに失敗して、errorが発行されています。
errorの内容は、safeParse({}).error?.issues
でアクセスできるので、コードを修正してエラー内容を確認します。
- console.log(user.safeParse({}))
+ console.log(user.safeParse({}).error?.issues)
実行するとこんな感じのログがでます
[
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'name' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'number',
received: 'undefined',
path: [ 'age' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'email' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'password' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'confirmPassword' ],
message: 'Required'
}
]
各フィールドで、要素が存在しないエラーが発生していますね。いい感じ
逆に、parseをpassする引数で実行してみます。
- console.log(user.safeParse({}).error?.issues)
+ console.log(user.safeParse({
+ name: 'name',
+ age: 999,
+ email: 'email',
+ password: 'password',
+ confirmPassword: 'confirmPassword',
+ }))
ログを確認します。
{
success: true,
data: {
name: 'name',
age: 999,
email: 'email',
password: 'password',
confirmPassword: 'confirmPassword'
}
}
success: true,
が返却されてます!いい感じですね。
現状では、stringやnumberなどの型チェックしか実装できていません。
次からは、その他validationを追加していきます!!!
Zod validation追加
zodでは、このようにメソッド形式でわかりやすくvalidationを追加できます。
では、Userに以下のvalidationを追加していきます!!!
名前:必須。
年齢:必須。20歳以上。80歳以下。
メールアドレス:必須。メアド形式必須。
パスワード:必須。8文字以上で英数字のみで構成されていること。
確認用パスワード:必須。パスワードと等しいこと。
Zod 必須化
zod.string()関数は第一引数にオブジェクトを受け取ります。
このinvalid_type_error や required_errorにエラーメッセージを追加することで、簡単にエラーメッセージが実装できます。
- const name = z.string();
+ const name = z.string({ required_error: "名前は必須です", });
+ console.log(name.safeParse(undefined).error?.issues)
tsxで実行
[
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [],
message: '名前は必須です'
}
]
良い感じですね!
つぎは年齢のvalidationを実装します!
名前:必須。
年齢:必須。20歳以上。80歳以下。
メールアドレス:必須。メアド形式必須。
パスワード:必須。8文字以上で英数字のみで構成されていること。
確認用パスワード:必須。パスワードと等しいこと。
Zod min, max
min
, max
関数で実装できます。
- const age = z.number();
+ const age = z.number().min(20, '年齢は20歳以上である必要があります').max(80, '年齢は80歳以下である必要があります');
+ console.log(age.safeParse(1).error?.issues)
+ console.log(age.safeParse(100).error?.issues)
+ console.log(age.safeParse(40))
[
{
code: 'too_small',
minimum: 20,
type: 'number',
inclusive: true,
exact: false,
message: '年齢は20歳以上である必要があります',
path: []
}
]
[
{
code: 'too_big',
maximum: 80,
type: 'number',
inclusive: true,
exact: false,
message: '年齢は80歳以下である必要があります',
path: []
}
]
{ success: true, data: 40 }
良い感じですね。
min
, max
関数はz.string
でも使用できます。
その場合は、min
, max
で文字数のlengthを判定できます。
- const name = z.string({ required_error: "名前は必須です", });
+ const name = z.string({ required_error: "名前は必須です", }).min(1, "1文字以上入力してください");
console.log(name.safeParse(""))
- // → { success: true, data: '' } // 空文字でも必須チェックはパスできる
+ // → { success: false, error: [Getter] } // 文字数が0扱いでエラー判定される
これでnameフィールドもageフィールドも良い感じですね。
つぎはメールアドレス形式かどうかのチェックを実装してみます!!
よくあるやつ!!
Zod よくある形式指定
いろいろ対応してますね
- const email = z.string();
+ const email = z.string().email('メールアドレス形式で入力してください');
+ console.log(email.safeParse("aaa@aaa"))
+ console.log(email.safeParse("aaa@aaa.com"))
+ console.log(email.safeParse("aaa+123@aaa.com"))
pnpm tsx src/hello_zod.ts で実行します
{ success: false, error: [Getter] }
{ success: true, data: 'aaa@aaa.com' }
{ success: true, data: 'aaa+123@aaa.com' }
良い感じです。めっちゃ簡単ですね。
zodにはほかにも便利なvalidationがたくさんあります。
では次はパスワードの形式チェックです。
よくある正規表現チェックですね。
Zod 正規表現
zodでは正規表現は regex
関数で実装できます。
- const password = z.string();
+ const password = z.string().regex(/^[a-zA-Z0-9]{8,}$/, 'パスワードは英数字のみを使用し、8文字以上入力してください');
+ console.log(password.safeParse("password"))
+ console.log(password.safeParse("pass1234"))
+ console.log(password.safeParse("pass123"))
+ console.log(password.safeParse("pass!word123"))
↓↓↓
{ success: true, data: 'password' }
{ success: true, data: 'pass1234' }
{ success: false, error: [Getter] }
{ success: false, error: [Getter] }
良い感じですね。
最後に確認用パスワードのバリデーションを行います。
Zod 相関validation
相関validationは、Userにコードを追加して実装します。
const user = z.object({
name,
age,
email,
password,
confirmPassword,
});
zodには、データを受け取って、カスタムなチェックを行うrefine
, superRefine
という関数が存在します。
この関数を使用してみます。
const user = z.object({
name,
age,
email,
password,
confirmPassword,
- });
+ }).refine((data) => data.password === data.confirmPassword, {
+ message: '入力されたパスワードと確認用パスワードが異なります。'
+ });
refine
関数は第一引数にカスタムvalidationロジックを実装した関数をとり、
第二引数にエラーメッセージなどの情報を持つオブジェクトをとります。
今回は、dataで受け取った値に存在するdata.password と data.confirmPassword を比較しています。
テストしてみます。
console.log(user.safeParse({
name: 'aaa',
age: 40,
email: 'email@qiita.com',
password: 'password',
confirmPassword: 'password',
}))
// ↓
// {
// success: true,
// data: {
// name: 'aaa',
// age: 40,
// email: 'email@qiita.com',
// password: 'password',
// confirmPassword: 'password'
// }
// }
console.log(user.safeParse({
name: 'aaa',
age: 40,
email: 'email@qiita.com',
password: 'password',
confirmPassword: 'pa$$word',
}).error?.issues)
// ↓
// [ { code: 'custom', message: '入力されたパスワードと確認用パスワードが異なります。', path: [] } ]
良い感じですね。refineでなんでもやれそうです!!
ただ、path:[] が見えると思います。
pathとは、zodのどの項目のエラーとするか?を指定する場所です。
やろうと思えば、このエラーをnameのエラーにできたりします。
このエラーは確認用パスワードのエラーとしたいので、pathにはconfirmPasswordを指定します。
const user = z.object({
name,
age,
email,
password,
confirmPassword,
}).refine((data) => data.password === data.confirmPassword, {
+ path: ['confirmPassword'],
message: '入力されたパスワードと確認用パスワードが異なります。'
});
これで完成です!!
名前:必須。
年齢:必須。20歳以上。80歳以下。
メールアドレス:必須。メアド形式必須。
パスワード:必須。8文字以上で英数字のみで構成されていること。
確認用パスワード:必須。パスワードと等しいこと。
これで、上記Formを作成する前準備としてzodのオブジェクトを定義できました。
Form側で使用したいので、userと型情報をexportしておきます。
zodオブジェクトのtypeへの変換は、z.infer<typeof XXXX>
を使用します。
これで下準備は完了しました。
最終的なコードを乗せておきます。
import { z } from "zod";
// 名前フィールド
const name = z.string({ required_error: "名前は必須です", }).min(1, "1文字以上入力してください");
// 年齢フィールド(20-80歳)
const age = z.number().min(20, '年齢は20歳以上である必要があります').max(80, '年齢は80歳以下である必要があります');
// メールアドレスフィールド
const password = z.string().regex(/^[a-zA-Z0-9]{8,}$/, 'パスワードは英数字のみを使用し、8文字以上入力してください');
// パスワードフィールド(8文字以上、英数字のみ)
const password = z.string().regex(/^[a-zA-Z0-9]{8,}$/);
// 確認用パスワードフィールド
import { z } from "zod";
// 名前フィールド
const name = z.string({ required_error: "名前は必須です", }).min(1, "1文字以上入力してください");
// 年齢フィールド(20-80歳)
const age = z.number().min(20, '年齢は20歳以上である必要があります').max(80, '年齢は80歳以下である必要があります');
// メールアドレスフィールド
const email = z.string().email('メールアドレス形式で入力してください');
// パスワードフィールド(8文字以上、英数字のみ)
const password = z.string().regex(/^[a-zA-Z0-9]{8,}$/, 'パスワードは英数字のみを使用し、8文字以上入力してください');
// 確認用パスワードフィールド
const confirmPassword = z.string();
export const user = z.object({
name,
age,
email,
password,
confirmPassword,
}).refine((data) => data.password === data.confirmPassword, {
path: ['confirmPassword'],
message: '入力されたパスワードと確認用パスワードが異なります。'
});
// zodスキーマオブジェクトを型情報に変換してexport
export type User = z.infer<typeof user>;
あとはこの型をReact Hook Formで使用してあげるだけです!!
React Hook Form + Zod で簡単フォーム作成&バリデーション
React Hook Formには、@hookform/resolvers
という関連ライブラリが存在します。
zodやその他のvalidationライブラリとReact Hook Formをコラボさせるためのライブラリです。
今回はこのライブラリを使用し、React Hook Form と Zodを連携させます。
Zod resolvers ライブラリ用意
ライブラリを追加します。
pnpm add @hookform/resolvers
Packages: +1
+
Progress: resolved 231, reused 193, downloaded 0, added 1, done
dependencies:
+ @hookform/resolvers 3.9.0
Done in 1.9s
良い感じです。
公式のドキュメントを確認します。
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
const schema = z.object({
name: z.string().min(1, { message: 'Required' }),
age: z.number().min(10),
});
const App = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit((d) => console.log(d))}>
<input {...register('name')} />
{errors.name?.message && <p>{errors.name?.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age?.message && <p>{errors.age?.message}</p>}
<input type="submit" />
</form>
);
};
ふむふむ。
} = useForm({
resolver: zodResolver(schema),
});
なんとこれだけで良いらしいです。すげー
{errors.age?.message && <p>{errors.age?.message}</p>}
エラーメッセージはこんな感じでアクセスするようです。
ではこのソースを改造する形で、React Hook Form + Zod でのフォームを実装してみます!!
実装
まずは上記ドキュメントからコピペしたコードを少し変更しました。
先ほどexportしたUserの型やzodスキーマをimportし、使用します。
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
- import * as z from 'zod'; // インポートされたスキーマを使用するため削除
-
- const schema = z.object({
- name: z.string().min(1, { message: 'Required' }),
- age: z.number().min(10),
- });
+ import { User, user } from '../hello_zod'
- const App = () => {
+ export default function App() { // とりあえず統一
const {
register,
handleSubmit,
formState: { errors },
- } = useForm({
- resolver: zodResolver(schema),
+ } = useForm<User>({ // 型を指定
+ resolver: zodResolver(user),
});
return (
- <form onSubmit={handleSubmit((d) => console.log(d))}>
+ <form onSubmit={handleSubmit((d) => console.log(d), (d) => console.log(d))}> // Invalid時もログを表示する
<input {...register('name')} />
{errors.name?.message && <p>{errors.name?.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age?.message && <p>{errors.age?.message}</p>}
<input type="submit" />
</form>
);
};
こんな変更を加えました。
変更理由はコメントの通りです。diffしないとこんな感じです。
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { User, user } from '../hello_zod'
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<User>({
resolver: zodResolver(user),
});
return (
<form onSubmit={handleSubmit((d) => console.log(d), (d) => console.log(d))}>
<input {...register('name')} />
{errors.name?.message && <p>{errors.name?.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age?.message && <p>{errors.age?.message}</p>}
<input type="submit" />
</form>
);
};
足りてないinputはいろいろあるんですが、とりあえず動かしてみましょ!
import ZodForm from "./components/ZodForm"
function App() {
return (
<>
<ZodForm />
</>
)
}
export default App
App.tsxで表示されるよう編集し、submitしてみました。
Zodで設定したエラーが良い感じに出ています!!!
無事React Hook Form + Zod の統合がうまくいきました。
あとはinput項目などをこねこねするだけですね。
雑にこんな感じのtsxを作成しました。
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { User, user } from '../hello_zod'
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<User>({
resolver: zodResolver(user),
});
return (
<form onSubmit={handleSubmit((d) => console.log(d), (d) => console.log(d))}>
<input {...register('name')} />
{errors.name?.message && <p>{errors.name?.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age?.message && <p>{errors.age?.message}</p>}
<input {...register('email')} />
{errors.email?.message && <p>{errors.email?.message}</p>}
<input {...register('password')} />
{errors.password?.message && <p>{errors.password?.message}</p>}
<input {...register('confirmPassword')} />
{errors.confirmPassword?.message && <p>{errors.confirmPassword?.message}</p>}
<input type="submit" />
</form>
);
};
テストしましょう!!!
めっちゃいい!!!
この後はスタイリングや、inputタグやエラーメッセージのコンポーネント化をしていけばいい感じのフォームの実装が完了しますね~
終わりに
今回はReact Hook Form + Zod でフォーム、validationの実装をしてみました。
記事用に書いたコードは簡単に検証するためのものですが、他にもいろいろ試せて楽しかったです。
興味のある方はリポジトリ覗いてみてください!