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
相关产品推荐
相关产品推荐

