Closed7

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

masa5714masa5714

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

https://ui.shadcn.com/docs

コードハイライトが効かないのとGithubでサンプルコードページまで辿るのが面倒なので...。

masa5714masa5714

フォームの基本

"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>
  );
}
  1. バリデーションルールの定義をする。
  2. zodの準備をする(バリデーションルールと初期値の指定)
  3. FormField コンポーネントの name 属性と render を記述する。

name 属性は z.object で定義した名前を使う。
render は出力したいHTMLを記述する。 <FormMessage /> にはバリデーションエラー時のメッセージが出力される。

Enterでフォームを送信したい場合

<Button type="submit">保存する</Button>

に変更すればOKです。

masa5714masa5714

Input

生HTMLのinput要素とほぼ同じような使い方ができる。

https://ui.shadcn.com/docs/components/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>
  )}
/>
masa5714masa5714

Textarea(テキストエリア)

https://ui.shadcn.com/docs/components/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>
  )}
/>
masa5714masa5714

Switch(iOSっぽいトグルボタンをイメージすればOK)

https://ui.shadcn.com/docs/components/switch

"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)

masa5714masa5714

Checkbox

想像通りの一般的なチェックボックス

https://ui.shadcn.com/docs/components/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)反映されるようになる。

masa5714masa5714

数値のバリデーション

price: z.preprocess((v) => Number(v), z.number().min(0)),

このようにして、Input要素で type="text" としておけばOK。
この場合の min(0) は 「0以上」である。

type="number" はマウスホイールの事故が起きるので使いたくない。

このスクラップは2024/03/08にクローズされました