Torihaji's Growth Diary

Little by little, no hurry.

Nextjs x RHF x Zodを使って フォームを作ってみる part1

はじめに

みなさん、こんにちは torihaziです

今日はNextjs と RHF(react-hook-form)とZodを使って

フォームと入力成功したら簡単なページを表示するような小規模アプリを

練習がてら作っていこうと思います

環境構築はDocker使って、デプロイはvercelとか使えたらなと思ってます

どこまでできるかは気力次第です。

スピード感持ってやっていこうと思います

今 朝の9:33なので 11:30までにはいけたらなと。

ではltg

環境構築

ターミナルで作業ディレクトリ作った後に Dockerfileとdocker-compose.yml作ります。

mkdir next_practice && cd next_practice
code .

開かれたvscodecontrol + @してターミナル開いてDockerfileとdocker-compose.yml作って buildします

touch Dockerfile docker-compose.yml
内容記載する
docker compose build
docker compose run --rm front bash

Dockerfile

FROM node:22

WORKDIR /app

EXPOSE 3000

docker-compose.yml

version: "3"
services:
  front:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 3000:3000
    volumes:
      - .:/app
    container_name: front
    command: /bin/sh -c "cd next_form && npm run dev"

buildができたら次です。

docker compose run --rm front bash
npx create-next-app@latest --typescript
✔ What is your project named? … next_form
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to customize the default import alias (@/*)? … No 

そうするとnext_formというディレクトリがDockerfileとかある階層に作られます。 できたら1度コンテナを抜けて docker compose up -dで立ち上げます。

http://localhost:3000/ に下記画面が出てれば環境構築終了です。

一応githubの設定も済ませておきましょう。

コンテナから抜けてnext_form ディレクトリに移動して git initして

githubに設定して繋げます。

ログインページの実装

仕様はシンプルです。api通信等はしません。

あらかじめ設定してある値を入力すればページ遷移するし、しなければしません。

まず react-hook-formとzodを入れます。

npm install react-hook-form zod @hookform/resolvers

ということで pagesディレクトリ配下にlogin.tsxを作成します。

スキーマとかの作成も同じページに書いちゃいます。

簡単にまずは名前とメアドくらいから始めます。

スキーマとかも同じページに書きます。

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";

const loginSchema = z.object({
  name: z.string().min(1, "Required"),
  email: z.string().min(1, "Required").email("Invalid email Address"),
});

type LoginSchemaType = z.infer<typeof loginSchema>;

export default function Login() {
  const form = useForm<LoginSchemaType>({
    mode: "onChange",
    resolver: zodResolver(loginSchema),
  });

  return <>{console.log(form)}</>;
}

こんな感じで書いて http://localhost:3000/login 覗いてみて検証ツール開きます。

なんかこのformっていうobjectの中に色々入ってるみたいですね。

というかよくconst { register } = useForm()とかしてregister分割代入で受け取るのは

こういうことだったのですね。

今知りました笑 

なんとなく何も考えずに前例習って使ってましたがこれで納得です。

ということでログインフォーム作ってボタン用意して実際にsubmitできるところまで行きましょう。

まずは最初のuseFormのところを修正します。

今回は registerとhandleSubmitを使います。

register メソッドは react-hook-form側に どの項目を登録させるかを指定するために使います。

handleSubmit メソッドはフォームのデータをsubmitする上で必要になるものです。

handleSubmitメソッドは 引数としてコールバック関数を2つ受け取ることが可能です。

1つがバリデーションが通ったら発火するonSuccessというもの。

もう1つがバリデーションが通らなかったら発火するonErrorというもの。

onSuccessとかの名前は非公式だと思います。わかりやすいのであえてつけてます。

onSuccessは引数にバリデーションが通ったフォームのデータ(data)とイベントオブジェクト(e)を受け取れます。

このうち必須なのは dataです。

一方、onErrorは引数にバリデーションが通らなかったとき項目別に格納されたエラー(errors)とイベントオブジェクト(e)を受け取れます。

こちらも必須なのは errorsです。

公式ドキュメントにも書いてありますのでそちらも見てみてください

ということで型をガッチガチにして書いた(つもりの)修正版が下記です。

export default function Login() {
  const { register, handleSubmit } = useForm<LoginSchemaType>({
    mode: "onChange",
    resolver: zodResolver(loginSchema),
  });

  const onSuccess: SubmitHandler<LoginSchemaType> = (
    data: LoginSchemaType,
    e?: BaseSyntheticEvent
  ) => console.log(data);

  const onError: SubmitErrorHandler<LoginSchemaType> = (
    errors: FieldErrors<LoginSchemaType>,
    e?: BaseSyntheticEvent
  ) => console.log(errors);

  return (
    <div className="flex items-center justify-center h-screen">
      <form
        onSubmit={handleSubmit(onSuccess, onError)}
        className="flex flex-col gap-2"
      >
        <input {...register("name")} className="border" />
        <input {...register("email")} className="border" />
        <button className="border hover:bg-slate-300 transition-all">
          送信
        </button>
      </form>
    </div>
  );
}

よくあるのが

onSubmit={handleSubmit(onSubmit)}

みたいな書き方ではないでしょうか。

これで

const onSubmit = (values) => 送信する処理

みたいな感じですね。

自分はこのvaluesがどっから出てきたのか知らず、とりあえずhandleSubmitにonSubmit渡して

中で valuesを引数に渡したらなんか動くんでしょ、

みたいな理解で8月生きてましたが、

これでようやく理解しました。

TypescriptのSubmitHandlerとかいう型に全部書いてあったし、

公式ドキュメントにもまんま書いてあったんですね。

というか慣習か何かで onSubmitってやっているのだと思いますが、

だったらonSuccessの方がわかりやすくて良いのでは。。

まぁそこは置いといてこれで動くと思うので 動かしてみましょう。

http://localhost:3000/login に移動したら画面の真ん中にこんなものが表示されると思います。

試しに画面入力何もしないで送信押してみます。

画面は変わらずですが、検証ツール見てみると

なんか出てますね。 プロパティ名がinputで指定したnameとemailで

その中にRequiredとかエラーメッセージぽいのが入ってるオブジェクトが帰ってきますね。

とりあえず zodのスキーマ自体は機能していそうです。

emailは入力必須に加えて形式もルールにあるので nameとemailを適当に入力して再度送信押すと

nameに関するものが消えて emailの形式が不正ですみたいな文言を含んだものが返ってきましたね。

じゃあ最後に成功例も見てみましょう。

name: hoge , email: hoge@mail.comとしたらどうなるか、

出ましたね、成功です。

ただエラーメッセージがUIに出ないのはよろしくないので次回はそれを出せるようにといったような

エラーメッセージの表示方法やバリデーションのタイミングについて

もう少し深掘りしていこうと思います。

終わりに

はい、最初に11時に終わるとかいってましたが

nextjsの環境構築する際のdockerfileとか作るところで udemyとかみて

詰まってました。調子乗りました。

調べたらすぐ出てくるのでコピペしたらいいんですけど

気持ち悪さが拭えなかったのでそこは耐えました。

この回での発見は やっぱりonSubmitの謎です。

だからSubmitHandlerとかを実務ではつけてたし、dataも使えていたのですね。

初心者であるのは別に悪いことだとは思いませんが、

やっぱり無知は罪になりうることもまた確かなので

そこは学習を続けて早く高みに行きたいなと思いました。

ということで少し休憩して、続きやります。

では。