Closed7
shadcn/uiの各コンポーネントと react-hook-form + zodの繋ぎ合わせ

shadcn/ui の公式ドキュメントを読めばいいのだが、
バリデーション付きのフォームを実装する際に各コンポーネントのコードを読む必要がある。やや面倒くさいのでいつでもサッと理解できるように書き出しておきたい。
コードハイライトが効かないのとGithubでサンプルコードページまで辿るのが面倒なので...。

フォームの基本
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
// バリデーションルールの定義
const formSchema = z.object({
name: z.string().trim().min(1) // trim後の文字列が1文字以上入力されている
});
export const MyForm = () => {
// zodの準備
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { // フォームの初期値を指定
name: ""
}
});
function onSubmit(values: z.infer<typeof formSchema>) {
// バリデーションが成功したときだけ動く
// バリデーションルールに定義したtrim()処理が終わった状態の値が入ってくれている。
console.log(values, "バリデーション結果"); }
return (
<Form {...form}>
<form>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>お名前</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="button" onClick={form.handleSubmit(onSubmit)}>
保存する
</Button>
</form>
</Form>
);
}
- バリデーションルールの定義をする。
- zodの準備をする(バリデーションルールと初期値の指定)
-
FormField
コンポーネントのname
属性とrender
を記述する。
name
属性は z.object
で定義した名前を使う。
render
は出力したいHTMLを記述する。 <FormMessage />
にはバリデーションエラー時のメッセージが出力される。
Enterでフォームを送信したい場合
<Button type="submit">保存する</Button>
に変更すればOKです。

Input
生HTMLのinput要素とほぼ同じような使い方ができる。
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
// バリデーションルールの定義
const formSchema = z.object({
name: z.string().trim().min(1)
});
// 初期値の指定
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: ""
}
});
...中略
// JSX
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>お名前</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
</FormItem>
)}
/>

Textarea(テキストエリア)
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
// バリデーションルールの定義
const formSchema = z.object({
note: z.string().trim()
});
// 初期値の指定
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
note: ""
}
});
...中略
// JSX
<FormField
control={form.control}
name="note"
render={({ field }) => (
<FormItem>
<FormLabel>注意事項</FormLabel>
<FormControl>
<Textarea placeholder="何かあれば記載する" {...field} />
</FormControl>
</FormItem>
)}
/>

Switch(iOSっぽいトグルボタンをイメージすればOK)
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
// バリデーションルールの定義
const formSchema = z.object({
agree: z.boolean()
});
// 初期値の指定
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
agree: false
}
});
...中略
// JSX
<FormField
control={form.control}
name="agree"
render={({ field }) => (
<FormItem>
<FormLabel>同意</FormLabel>
<FormControl>
<FormField
control={form.control}
name="agree"
render={({ field }) => (
<FormItem>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
</FormControl>
</FormItem>
)}
/>
ポイント
checked={field.value} onCheckedChange={field.onChange}
を付与することでトグルボタンの値が反映されるようになる。(true / false)

Checkbox
想像通りの一般的なチェックボックス
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Checkbox } from "@/components/ui/checkbox";
// バリデーションルールの定義
const formSchema = z.object({
agree: z.boolean()
});
// 初期値の指定
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
agree: false
}
});
...中略
// JSX
<FormField
control={form.control}
name="agree"
render={({ field }) => (
<FormItem>
<FormLabel>同意</FormLabel>
<FormControl>
<FormField
control={form.control}
name="agree"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex items-center gap-x-2">
<Checkbox checked={field.value} onCheckedChange={field.onChange} id="checkbox-agree" />
<label htmlFor="checkbox-agree">同意する</label>
</div>
</FormControl>
</FormItem>
)}
/>
</FormControl>
</FormItem>
)}
/>
ポイント
Checkbox
コンポーネントに checked={field.value} onCheckedChange={field.onChange}
を付与することで値が(true / false)反映されるようになる。

数値のバリデーション
price: z.preprocess((v) => Number(v), z.number().min(0)),
このようにして、Input要素で type="text"
としておけばOK。
この場合の min(0)
は 「0以上」である。
type="number" はマウスホイールの事故が起きるので使いたくない。
このスクラップは2024/03/08にクローズされました