Next.js(App Router) + Zod + Prisma+ React Hook FormでTODOリストを作りました

2024/08/04に公開

背景

React Hook Formの良さを体感すべく、React Hook Formを使った時と使わなかった時のTODOリストを実装します。

当記事は、React Hook Formを使った場合の実装します。
GitHubはこちら

1. プロジェクトのセットアップ

npx create-next-app@latest todo-list-next-and-react-hook-form
cd todo-list-next-and-react-hook-form

プロンプトでは以下のように選択してください:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes
  • src/ directory: Yes
  • App Router: Yes
  • Import alias: No

2. 必要なパッケージをインストール

npm install @prisma/client zod
npm install -D prisma
npm install react-hook-form @hookform/resolvers zod

3. Docker Compose ファイルの作成

プロジェクトのルートにdocker-compose.ymlファイルを作成します。

docker-compose.yml
version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_USER: todouser
      POSTGRES_PASSWORD: todopassword
      POSTGRES_DB: todo_db
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

4. PostgreSQL の起動

docker-compose up -d

5. Prismaの初期化

npx prisma init

6. .envファイルを編集

DATABASE_URL="postgresql://todouser:todopassword@localhost:5432/todo_db?schema=public"

7. prisma/schema.prismaファイルを編集

prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Todo {
  id        Int      @id @default(autoincrement())
  title     String
  completed Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

8. データベースのマイグレーションを実行

npx prisma migrate dev --name init

9. next.config.jsファイルを編集してサーバーアクションを有効化

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
}

module.exports = nextConfig

10. コードを書く

GitHub参照

それでは、React Hook Formを使わなかった時の実装と比較して違うところを書いていきます。

1. Form用のSchemaが追加される

import { z } from "zod";

export const TodoSchema = z.object({
  id: z.number().optional(),
  title: z
    .string()
    .min(1, "Title is required")
    .max(100, "Title must be 100 characters or less"),
  completed: z.boolean().default(false),
});

// 追加された
export const TodoFormSchema = TodoSchema.omit({ id: true, completed: true });

export type Todo = z.infer<typeof TodoSchema>;
// 追加された
export type TodoFormData = z.infer<typeof TodoFormSchema>;

2. React Hook Formを使用してフォームが実装される

"use client";

import { addTodo } from "@/app/actions";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { TodoFormSchema, TodoFormData } from "@/lib/validate";

export function AddTodoForm() {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<TodoFormData>({
    resolver: zodResolver(TodoFormSchema),
  });

  const onSubmit = async (data: TodoFormData) => {
    await addTodo(data);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="flex gap-2">
      <input
        {...register("title")}
        placeholder="New todo"
        className="flex-grow px-2 py-1 border rounded text-gray-800"
      />
      <button
        type="submit"
        className="px-4 py-1 bg-blue-500 text-white rounded"
      >
        Add
      </button>
      {errors.title && (
        <p className="text-red-500 text-sm">{errors.title.message}</p>
      )}
    </form>
  );
}

11. アプリケーションの起動

npm run dev

補足

開発が終わったら、Dockerコンテナを停止します。

1. Dockerコンテナの停止

Dockerコンテナを停止したい場合は、以下のコマンドを使用します。

docker-compose down

2. Dockerコンテナの停止して、データを完全に削除したい場合

-vオプションを追加します

docker-compose down -v

まとめ

私が体感したReact Hook Formを使った時と使わなかった時の違いは、以下2つでした。

  • React Hook Formは入力中や送信前にzodのバリデーションを行ってくれる
  • React Hook Formはエラーの状態管理が自動的に行われる

関連記事

Next.jsを使った開発における勘所を鍛える
React + zod + Bun + PrismaでTODOリストを作りました
Next.js(App Router) + zod + PrismaでTODOリストを作りました
Next.js(App Router) + zod + Prisma+React Hook FormでTODOリストを作りました
GitHub

Discussion