はじめに
part2続きです。
githubです。
次は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 にあげるの忘れないでくださいね。