Zenn
💨

shadcn/ui + React Hook Form + Zod で作る簡単な入力フォーム

2024/12/30に公開

はじめに

shadcn/uiが提供するフォーム関連コンポーネントを使いながら、React Hook Form + Zodを組み合わせてバリデーション付きのフォームを作成する方法をご紹介します。

shadcn/uiとは

Tailwind CSS + Radix UIをベースにしたコンポーネントライブラリ。フォーム周りのコンポーネントも整っているため、UI/UXを向上させやすい。
公式ドキュメントでは、各コンポーネントの使い方が詳しく解説されています。

前提条件

  • Reactプロジェクトが作成されていること
  • shadcn/uiの導入が完了していること
    完了していない場合は、下記を参考に導入する。
    https://ui.shadcn.com/docs/installation

事前準備

フォーム関連コンポーネントの追加

npx shadcn-ui add form input button label textarea

React Hook Form + Zod のインストール

npm install react-hook-form zod @hookform/resolvers
  • React Hook Form
    公式推奨のフォームライブラリ。軽量かつ柔軟なフォーム制御を可能にし、バリデーションとの連携がスムーズです。

  • Zod
    スキーマに基づくバリデーションをシンプルに書けるライブラリ。React Hook Form と合わせることで、フロントエンド側での入力チェックをわかりやすく定義できます。

フォーム実装

バリデーションスキーマ定義

Zodを用いて、入力項目ごとのバリデーションルールを定義します。
例えばタイトルと内容を入力するフォームの場合、以下のようにスキーマを定義できます。

customForm/formSchema.ts
import { z } from "zod";

export const formSchema = z.object({
  title: z
    .string()
    .max(50, { message: "タイトルは50文字以内で入力してください" })
    .nonempty({ message: "タイトルを入力してください" }),

  description: z
    .string()
    .max(200, { message: "内容は200文字以内で入力してください" })
    .nullable(),
});

z.object({ ... })の中に、各項目ごとの検証ルールを書きます。

  • max(50) などのルール: 文字数制限を設定
  • nullable(): データが null でも許容する
  • nonempty(): 空文字チェック
  • refine(...): より細かな条件チェック。今回は null を許容しないという条件を追加しています。

フォームコンポーネント作成

customForm/index.tsx
export function CustomForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: "",
      description: "",
    },
  });

  // フォーム送信時の処理
  const onSubmit = (values: z.infer<typeof formSchema>) => {
    console.log(values);
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="title"
          render={({ field }) => (
            <FormItem>
              <FormLabel>タイトル</FormLabel>
              <FormControl>
                <Input {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="description"
          render={({ field }) => (
            <FormItem>
              <FormLabel>内容</FormLabel>
              <FormControl>
                <Textarea {...field} value={field.value ?? ""} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">登録</Button>
      </form>
    </Form>
  );
}

React Hook FormのuseForm

  • resolver: zodResolver(formSchema) を指定することで、「Zod のルールに従ってチェックする」ようになります。
  • defaultValues で「フォームの初期値」を指定できます。

shadcn/uiのコンポーネント

<Form><FormField><FormItem>などを使うと、フォームの構造がわかりやすく整理されます。
<FormMessage>が自動的にエラーメッセージを表示してくれるので、入力ルールに違反したときにユーザーに知らせやすいです。

実行

下記でサーバー起動し、画面を確認。

npm run dev

画面

動作確認

何も入力せずに「登録」ボタンを押下すると...

バリデーションチェックに引っかかりエラーとなる。

任意の値を入力し、「登録」ボタンを押下すると...


バリデーションチェックに引っかからずに処理が正常に終了する。

まとめ

複雑なバリデーションロジックをスキーマ管理できたり、フォームのエラーや状態管理をスムーズに書けたりと様々なメリットが得られるのでぜひご活用ください。
慣れてきたら、入力項目を増やしたり、日付チェックやセレクトボックスなど、色んなケースに応用してみてください。
ありがとうございました!

Discussion

ログインするとコメントできます