zod
, MUI
, react-hook-form
を使って典型的なフォームを作成しようと思います。
Nextjs(page router)でコードは試しています。
環境
zod 3.23.6
@mui/material 5.15.13
next 14.1.3
react 18
react-hook-form 7.51.4
ユーザー名、メールアドレス、パスワード、性別をの情報を入力するフォームを想定します。
フォームの内容を定義
const Genders = {
Male: "male",
Female: "female",
Other: "other",
} as const;
type Gender = (typeof Genders)[keyof typeof Genders];
const GenderLabels: Record<Gender, string> = {
[Genders.Male]: "男性",
[Genders.Female]: "女性",
[Genders.Other]: "その他",
};
const registrationSchema = z.object({
username: z.string().min(3, "ユーザー名は3文字以上でなければなりません"),
email: z.string().email("正しいメールアドレスを入力してください"),
password: z.string().min(6, "パスワードは6文字以上でなければなりません"),
gender: z.custom<Gender>(),
});
type RegistrationFormValues = z.infer<typeof registrationSchema>;
registrationSchema
にバリデーション内容を定義しz.infer
でフォームの型を取得できます。
性別の情報はGender
型で定義しています。
フォームの制御に必要なものを取得
react-hook-form
からformの制御を行うために必要なものを取得します。
const Page: NextPageWithLayout = () => {
const {
reset,
control,
handleSubmit,
formState: { errors },
} = useForm<RegistrationFormValues>({
resolver: zodResolver(registrationSchema),
defaultValues: {
username: "",
email: "",
password: "",
gender: Genders.Other,
},
});
}
UIの構築
mui
を使ってフォームのUIを構築します。
const Page: NextPageWithLayout = () => {
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Box display={"flex"} flexDirection={"column"} gap={2}>
<Controller
name="username"
control={control}
render={({ field }) => (
<TextField
{...field}
label="ユーザー名"
error={!!errors.username}
helperText={errors.username?.message}
/>
)}
/>
<Controller
name="email"
control={control}
render={({ field }) => (
<TextField
{...field}
label="メールアドレス"
error={!!errors.email}
helperText={errors.email?.message}
/>
)}
/>
<Controller
name="password"
control={control}
render={({ field }) => (
<TextField
{...field}
label="パスワード"
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
<FormControl component="fieldset" error={!!errors.gender}>
<FormLabel component="legend">性別</FormLabel>
<Controller
name="gender"
control={control}
render={({ field }) => (
<RadioGroup
row
{...field}
onChange={(e) => {
field.onChange(e.target.value as Gender);
}}
>
{Object.entries(GenderLabels).map(([value, label]) => (
<FormControlLabel
key={value}
value={value}
label={label}
control={<Radio />}
/>
))}
</RadioGroup>
)}
/>
{errors.gender && <div>{errors.gender.message}</div>}
</FormControl>
<Button type="submit" variant="contained" color="primary">
登録
</Button>
<Image src={"https://source.unsplash.com/random"} alt="" width={200} height={40} />
</Box>
</form>
);
}
フォームに初期値を設定する
業務ではAPIから値を取得してフォームの初期値に設定するという場面があると思います。
初期値の設定にはreact-hook-form
から取得したreset
関数を使います。
const Page: NextPageWithLayout = () => {
useEffect(() => {
const fetchData = async () => {
setTimeout(() => {
reset({
username: "test",
email: "a@b.com",
password: "password",
gender: Genders.Male,
});
}, 1000);
};
fetchData();
}, [reset]);
}