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

如何使用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>
)}

大概的样子:

image.png
image.png

以下是完整代码,无法实现对 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;
阅读 4.4k
2 个回答

schema email 中添加 optional() 选项

image.png

image.png

修改过后的完整代码:

"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;
const UserForm = () => {
  const [variant, setVariant] = useState<"Login" | "Register">("Login");
  const [currentSchema, setCurrentSchema] = useState(LoginFormSchema);

  useEffect(() => {
    setCurrentSchema(variant === "Login" ? LoginFormSchema : RegisterFormSchema);
  }, [variant]);

  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch,
  } = useForm<FormSchemaType>({
    resolver: zodResolver(currentSchema),
    defaultValues: {
      username: "",
      password: "",
    },
  });

  // ...其它代码不变
};
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题