React Hook Form と Zod
React Hook Form はフォーム管理管理ライブラリ、
Zodはバリデーションライブラリです。
resolverライブラリを使って、React Hook Form と Zod を統合します。
Zod使用前のコード
import { SubmitHandler, useForm } from "react-hook-form";
// これから変更する点
type FormFields = {
email: string
password: string
};
const Form = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
setError,
} = useForm<FormFields>(); // これから変更する点
const onSubmit: SubmitHandler<FormFields> = async (data) => {
try {
const response = await fetch("https://example.com/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error("Failed to log in");
}
console.log("Login successful");
} catch (error) {
setError("root", { message: error.message || "Failed to log in" });
}
};
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
// これから変更する点
{...register("email", {
required: "Email is required",
validate: (value) => {
if (!value.includes("@")) {
return "Email must include @";
}
return undefined;
},
})}
/>
{errors.email && (
<div>{errors.email.message}</div>
)}
<input
type="text"
// これから変更する点
{...register("password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must have at least 8 characters",
},
})}
/>
{errors.password && (
<div>{errors.password.message}</div>
)}
<button
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? "Loading..." : "Submit"}
</button>
{errors.root && <div>{errors.root.message}</div>}
</form>
);
};
export default Form;
Zod使用後のコード
import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; // resolverのimport
import { z } from "zod"; // zのimport
// スキーマの作成
const schema = z.object({
email: z
.string()
.email("Please enter a valid email address")
.nonempty("Email is required"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.nonempty("Password is required"),
})
// 型はスキーマから推論
type FormFields = z.infer<typeof schema>
const Form = () => {
// スキーマを渡す
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
setError,
} = useForm<FormFields>({
resolver: zodResolver(schema)
});
const onSubmit: SubmitHandler<FormFields> = async (data) => {
try {
const response = await fetch("https://example.com/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error("Failed to log in");
}
console.log("Login successful");
} catch (error) {
setError("root", { message: error.message || "Failed to log in" });
}
};
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
// バリデーションはスキーマに移動したので削除
{...register("email")}
/>
{errors.email && (
<div>{errors.email.message}</div>
)}
<input
type="text"
// バリデーションはスキーマに移動したので削除
{...register("password")}
/>
{errors.password && (
<div>{errors.password.message}</div>
)}
<button
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? "Loading..." : "Submit"}
</button>
{errors.root && <div>{errors.root.message}</div>}
</form>
);
};
export default Form;
改善した点
改善した点は以下です。
- スキーマでバリデーションを作成したことによって、1箇所にまとめられた
- スキーマから型を推論することによって、型を自分で作成する必要がなくなった
- register部分にバリデーションを書く必要がなくなり、レンダリングの部分がすっきりした