如何使用react-hook-form+zod实现动态表单验证?

作者站长头像
站长
· 阅读数 11

如何使用react-hook-form+zod实现动态表单验证?比如登陆时不需要输入邮箱,注册时需要输入邮箱

{variant === "Register" && (
  <div className="space-y-2">
    <label htmlFor="email" className="inline font-semibold">
      邮箱
    </label>
    <input
      id="email"
      {...register("email", { shouldUnregister: true })}
      type="text"
      className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
    />
    {errors.email && (
      <p className="text-sm text-rose-500">{errors.email.message}</p>
    )}
  </div>
)}

大概的样子:

如何使用react-hook-form+zod实现动态表单验证?如何使用react-hook-form+zod实现动态表单验证?

以下是完整代码,无法实现对 email 项的校验

"use client";

import { useState } from "react";

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

const LoginFormSchema = z.object({
  username: z
    .string()
    .min(3, {
      message: "用户名不得少于3个字符",
    })
    .max(20, {
      message: "用户名不得多于20个字符",
    }),
  password: z
    .string()
    .min(8, {
      message: "密码不得少于8个字符",
    })
    .max(20, {
      message: "密码不得多于20个字符",
    }),
});

const RegisterFormSchema = z.object({
  email: z.string().email({
    message: "邮箱格式不正确",
  }),
  username: z
    .string()
    .min(3, {
      message: "用户名不得少于3个字符",
    })
    .max(20, {
      message: "用户名不得多于20个字符",
    }),
  password: z
    .string()
    .min(8, {
      message: "密码不得少于8个字符",
    })
    .max(20, {
      message: "密码不得多于20个字符",
    }),
});

const FormSchema = z.union([LoginFormSchema, RegisterFormSchema]);

type FormSchemaType = z.infer<typeof FormSchema>;

const UserForm = () => {
  const [variant, setVariant] = useState<"Login" | "Register">("Login");
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch,
  } = useForm<FormSchemaType>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      username: "",
      password: "",
    },
  });

  const onSubmit = async (data: FormSchemaType) => {
    await new Promise((resolve) => setTimeout(resolve, 1000));
  };

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-100">
      <div className="max-w-md flex-1">
        <form
          onSubmit={handleSubmit(onSubmit)}
          className="space-y-4 rounded-lg bg-white p-6 shadow-xl"
        >
          <h2 className="text-center text-xl font-semibold">
            {variant === "Login" ? "登录" : "注册"}
          </h2>
          {variant === "Register" && (
            <div className="space-y-2">
              <label htmlFor="email" className="inline font-semibold">
                邮箱
              </label>
              <input
                id="email"
                {...register("email", { shouldUnregister: true })}
                type="text"
                className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
              />
              {errors.email && (
                <p className="text-sm text-rose-500">{errors.email.message}</p>
              )}
            </div>
          )}
          <div className="space-y-2">
            <label htmlFor="username" className="inline font-semibold">
              用户名
            </label>
            <input
              id="username"
              {...register("username")}
              type="text"
              className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
            />
            {errors.username && (
              <p className="text-sm text-rose-500">{errors.username.message}</p>
            )}
          </div>
          <div className="space-y-2">
            <label htmlFor="password" className="inline font-semibold">
              密码
            </label>
            <input
              id="password"
              {...register("password")}
              type="password"
              className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
            />
            {errors.password && (
              <p className="text-sm text-rose-500">{errors.password.message}</p>
            )}
          </div>
          <p className="text-right text-sm">
            {variant === "Login" ? "还没有账号?去" : "已有账号?去"}
            <span
              className="cursor-pointer font-semibold underline"
              onClick={() =>
                setVariant(variant === "Login" ? "Register" : "Login")
              }
            >
              {variant === "Login" ? "注册" : "登录"}
            </span>
          </p>
          <button
            type="submit"
            className="w-full rounded-md bg-zinc-900 px-4 py-2 text-sm text-white hover:bg-zinc-900/90 disabled:cursor-not-allowed disabled:bg-gray-500"
            disabled={isSubmitting}
          >
            提交
          </button>
        </form>
        <code>
          <pre className="p-2">{JSON.stringify(watch(), null, 2)}</pre>
        </code>
      </div>
    </div>
  );
};

export default UserForm;
回复
1个回答
avatar
test
2024-06-27

schema email 中添加 optional() 选项

answer image

answer image

修改过后的完整代码:

"use client";

import { useState } from "react";

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

const FormSchema = z.object({
  email: z
    .string()
    .email({
      message: "邮箱格式不正确",
    })
    .optional(),
  username: z
    .string()
    .min(3, {
      message: "用户名不得少于3个字符",
    })
    .max(20, {
      message: "用户名不得多于20个字符",
    }),
  password: z
    .string()
    .min(8, {
      message: "密码不得少于8个字符",
    })
    .max(20, {
      message: "密码不得多于20个字符",
    }),
});

type FormSchemaType = z.infer<typeof FormSchema>;

const UserForm = () => {
  const [variant, setVariant] = useState<"Login" | "Register">("Login");
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch,
  } = useForm<FormSchemaType>({
    resolver: zodResolver(FormSchema),
  });

  const onSubmit = async (data: FormSchemaType) => {
    await new Promise((resolve) => setTimeout(resolve, 1000));
  };

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-100">
      <div className="max-w-md flex-1">
        <form
          onSubmit={handleSubmit(onSubmit)}
          className="space-y-4 rounded-lg bg-white p-6 shadow-xl"
        >
          <h2 className="text-center text-xl font-semibold">
            {variant === "Login" ? "登录" : "注册"}
          </h2>
          {variant === "Register" && (
            <div className="space-y-2">
              <label htmlFor="email" className="inline font-semibold">
                邮箱
              </label>
              <input
                id="email"
                {...register("email", { shouldUnregister: true })}
                type="text"
                className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
              />
              {errors.email && (
                <p className="text-sm text-rose-500">{errors.email.message}</p>
              )}
            </div>
          )}
          <div className="space-y-2">
            <label htmlFor="username" className="inline font-semibold">
              用户名
            </label>
            <input
              id="username"
              {...register("username")}
              type="text"
              className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
            />
            {errors.username && (
              <p className="text-sm text-rose-500">{errors.username.message}</p>
            )}
          </div>
          <div className="space-y-2">
            <label htmlFor="password" className="inline font-semibold">
              密码
            </label>
            <input
              id="password"
              {...register("password")}
              type="password"
              className="w-full rounded bg-gray-100 px-3 py-1.5 outline-none"
            />
            {errors.password && (
              <p className="text-sm text-rose-500">{errors.password.message}</p>
            )}
          </div>
          <p className="text-right text-sm">
            {variant === "Login" ? "还没有账号?去" : "已有账号?去"}
            <span
              className="cursor-pointer font-semibold underline"
              onClick={() =>
                setVariant(variant === "Login" ? "Register" : "Login")
              }
            >
              {variant === "Login" ? "注册" : "登录"}
            </span>
          </p>
          <button
            type="submit"
            className="w-full rounded-md bg-zinc-900 px-4 py-2 text-sm text-white hover:bg-zinc-900/90 disabled:cursor-not-allowed disabled:bg-gray-500"
            disabled={isSubmitting}
          >
            提交
          </button>
        </form>
        <code>
          <pre className="p-2">{JSON.stringify(watch(), null, 2)}</pre>
        </code>
      </div>
    </div>
  );
};

export default UserForm;
回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容