Torihaji's Growth Diary

Little by little, no hurry.

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

はじめに

part2続きです。

githubです。

GitHub - torihazi/next_form

次はinputの下にエラーメッセージ表示させてみることにします。

あとはpasswordを追加して、バリデーションに正規表現使ってみます。

続き

まずはパスワード追加します。

要件としては 8文字以上の英大文字、小文字、数字の組み合わせというよくあるやつです。

const loginSchema = z.object({
  name: z.string().min(1, "必須"),
  email: z.string().min(1, "必須").email("形式が不正です"),
  password: z
    .string()
    .min(8, "8文字以上")
    .regex(/^[a-zA-Z0-9]+$/, "英大文字、英小文字、数字の組み合わせ"),
});

正規表現について軽く説明すると/ が区切り文字で

^ が先頭、[]がその中のパターンのうち1文字、

a-z A-z 0-9が aからzまで AからZまで 0から9までで

+が直前の文字の1回以上の繰り返し、$が文末です。

間違ってたらすいません。

でこう書いたのでフォームを修正すると

    <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" placeholder="名前" />
        <input
          {...register("email")}
          className="border"
          placeholder="メールアドレス"
        />
        <input
          {...register("password")}
          className="border"
          placeholder="パスワード"
        />
        <button className="border hover:bg-slate-300 transition-all">
          送信
        </button>
      </form>
    </div>

です。ただpassword追加しただけです。

次。今回はユーザ体験向上週間なので、エラーメッセージを表示させたいと思います。

inputの下に軽く赤文字で入れる、でいいでしょう。

で、このformの状態を担当しているのがuseFormを実行した時のformStateとかいう人です。

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

console.log(formState)

てしてまた覗いてみると

何か色々入ってますね。ただ全部理解はここではしません。

今使うものだけです。

で今回表示させたいエラーの内容が入っているのがerrorsというもの。

ということでさっきのuseFormを

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginSchemaType>({
    mode: "onChange",
    resolver: zodResolver(loginSchema),
  });

とすればok

あとはerrorsの中身は1つ前で読んでいたonErrorに渡っていたerrorsと内容一緒なので

それを読んでinputの下に差し込むだけ。

{errors.name && <span className="text-red-500">{errors.name.message}</span>

みたいな形です。

    <div className="flex items-center justify-center h-screen">
      <form onSubmit={handleSubmit(onSuccess)} className="flex flex-col gap-2">
        <input {...register("name")} className="border" placeholder="名前" />
        {errors.name && (
          <span className="text-red-500">{errors.name.message}</span>
        )}
        <input
          {...register("email")}
          className="border"
          placeholder="メールアドレス"
        />
        {errors.email && (
          <span className="text-red-500">{errors.email.message}</span>
        )}
        <input
          {...register("password")}
          className="border"
          placeholder="パスワード"
        />
        {errors.password && (
          <span className="text-red-500">{errors.password.message}</span>
        )}
        <button className="border hover:bg-slate-300 transition-all">
          送信
        </button>
      </form>
    </div>

表示するとこんな感じ。

適当に入力して、送信ボタンを押さなくてもこんな感じで

エラーメッセージが表示されると思います。

これはなんでか。

これは最初のuseFormの宣言時に渡したmodeというプロパティに"onChange"を渡したから。

このmodeは最初のバリデーションのタイミングを設定できるプロパティであり、

デフォルトではonSubmitです。つまりsubmitしたら初めてバリデーションが働きます。

今回はよくあるものとして送信する前にユーザに教えてあげたいのでonChangeにしました。

onChangeにするとフォームの中に文字が入力されたらその時点でバリデーションが働きます。

ただこれトレードオフらしくて、onChangeはパフォーマンスが下がるらしいです。

変わるたびに再レンダリングが走るから。まぁそれは問題になったら考えます。

あとちなみに送信ボタン押したあとは入力した直後にバリデーションが働いています。

これはreValidateModeというものが原因です。

これがデフォルトではonChangeだからそうなっています。

useFormに引数として与えれば変更できます。

ということで今の時点でのコードがこれです。

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

const loginSchema = z.object({
  name: z.string().min(1, "必須"),
  email: z.string().min(1, "必須").email("形式が不正です"),
  password: z
    .string()
    .min(8, "8文字以上")
    .regex(/^[a-zA-Z0-9]+$/, "英大文字、英小文字、数字の組み合わせ"),
});

type LoginSchemaType = z.infer<typeof loginSchema>;

export default function Login() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = 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" placeholder="名前" />
        {errors.name && (
          <span className="text-red-500">{errors.name.message}</span>
        )}
        <input
          {...register("email")}
          className="border"
          placeholder="メールアドレス"
        />
        {errors.email && (
          <span className="text-red-500">{errors.email.message}</span>
        )}
        <input
          {...register("password")}
          className="border"
          placeholder="パスワード"
        />
        {errors.password && (
          <span className="text-red-500">{errors.password.message}</span>
        )}
        <button className="border hover:bg-slate-300 transition-all">
          送信
        </button>
      </form>
    </div>
  );
}

githubもあるのでみてみてください。

終わりに

ということでマリオでいう1-城くらいまではいったんじゃないでしょうか。

次はshadcn /uiとか噛ませてみますか

それかラジオボタンとかボタン押したらフォームが増えるやつとかですか。

まだまだ奥が深いです。

今回はこれくらいにして次に行きましょう。

あ、あとgit にあげるの忘れないでくださいね。