You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Zod Schema空字符串未触发required_error问题求助

问题

在Next.js应用中使用Zod验证订阅表单的email字段,预期字段为空时显示自定义错误信息「Email is required」,但点击「Subscribe」按钮时,触发的却是「Email is invalid」错误。

现有Schema定义

export const subscribeSchema = z.object({
  email: z
    .string({ required_error: "Email is required" })
    .email({ message: "Email is invalid" }),
});

表单实现代码

"use client";

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

import { cn } from "@/lib/util";
import { subscribeSchema } from "@/lib/validations/auth";
import { buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Icons } from "@/components/icons";
import { subscribeUser } from "@/lib/subscribe-user";

export type FormData = z.infer<typeof subscribeSchema>;

export function SubscribeForm() {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<FormData>({ resolver: zodResolver(subscribeSchema) });

  const [isLoading, setIsLoading] = React.useState<boolean>(false);
  const [error, setError] = React.useState<string | null>(null);

  const router = useRouter();

  async function onSubmit(submittedData: FormData) {
    await subscribeUser({
      submittedData,
      setError,
      router,
      setIsLoading,
      reset,
    });
  }

  return (
    <div className="grid gap-6 rounded-md border border-neutral-200 bg-neutral-100 p-6">
      <div className="flex flex-col space-y-1">
        <h1 className="text-2xl">Subscribe</h1>
        <p>Get notified of the course launch.</p>
      </div>
      {error && (
        <p className="text-sm text-red-500 animate-in fade-in-0 slide-in-from-left-1">
          {error}
        </p>
      )}
      <form onSubmit={handleSubmit(onSubmit)}>
        <div className="grid gap-6">
          <div className="grid gap-1">
            <Label htmlFor="email">Email</Label>
            <Input
              className="bg-white"
              id="email"
              type="email"
              placeholder="name@example.com"
              autoCapitalize="none"
              autoComplete="email"
              autoCorrect="off"
              disabled={isLoading}
              {...register("email")}
            />
            {errors.email && (
              <p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
                {errors.email.message}
              </p>
            )}
          </div>
          <button className={cn(buttonVariants())} disabled={isLoading}>
            {isLoading && (
              <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
            )}
            Subscribe
          </button>
        </div>
      </form>
    </div>
  );
}

解决方案

问题原因

当字段为空时,React Hook Form会将空字符串("")传递给Zod验证。此时Zod会先执行.string()验证——空字符串属于有效的string类型,不会触发required_error,接着执行.email()验证,空字符串不符合邮箱格式规则,因此触发「Email is invalid」错误。

修复方法

需要明确告诉Zod,空字符串属于无效输入,优先触发必填错误,有两种可行方式:

方式1:使用.nonempty()指定必填错误

在.string()之后添加.nonempty()方法,传入自定义必填错误信息:

export const subscribeSchema = z.object({
  email: z
    .string()
    .nonempty({ message: "Email is required" })
    .email({ message: "Email is invalid" }),
});

方式2:结合.refine()手动校验空值

如果需要更灵活的校验逻辑,可用.refine()手动判断空字符串:

export const subscribeSchema = z.object({
  email: z
    .string({ required_error: "Email is required" })
    .email({ message: "Email is invalid" })
    .refine(val => val.trim() !== "", { message: "Email is required" }),
});

额外优化:React Hook Form配置

可在useForm中开启mode: "onSubmit"确保验证只在提交时触发,同时给Input添加required属性增强原生表单提示:

// 在useForm配置中添加mode
const {
  register,
  handleSubmit,
  reset,
  formState: { errors },
} = useForm<FormData>({ 
  resolver: zodResolver(subscribeSchema),
  mode: "onSubmit" // 确保仅提交时验证
});

// Input组件添加required属性
<Input
  className="bg-white"
  id="email"
  type="email"
  placeholder="name@example.com"
  autoCapitalize="none"
  autoComplete="email"
  autoCorrect="off"
  disabled={isLoading}
  required // 原生必填提示
  {...register("email")}
/>

内容的提问来源于stack exchange,提问作者HKS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 03:25:08