LoginSignup
1
2

zod, MUI, react-hook-formを使ってフォームを作成する

Posted at

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]);
}
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2