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

React Hook Form + Zod 提交表单时控制台抛出校验错误但错误状态为空

React Hook Form + Zod 提交表单时控制台抛出校验错误但错误状态为空

看起来你这情况是自定义的Input组件没和React Hook Form(RHF)的register方法配合好,导致提交表单时Zod触发了邮箱校验错误,控制台抛了错,但页面上的错误状态却没正确显示出来。我帮你捋捋问题出在哪,怎么解决。

核心问题:自定义Input和RHF的状态不同步

你的Input组件自己维护了isFocused、hasValue这些状态,还解构了value、onChange这些props,但从你贴的截断代码来看,大概率是没把register返回的props正确传给内部的原生<input>元素,甚至可能覆盖了RHF的状态更新逻辑——这就导致RHF没法正确追踪输入框的实时值,提交时表单里的email值可能还是空的或者没更新,直接触发了Zod的邮箱格式校验错误,同时页面上的formState.errors因为组件没正确关联,也没把错误显示出来。

一步步解决问题

1. 修复自定义Input组件,正确绑定RHF的props

修改你的Input组件,确保把register返回的所有属性(包括value、onChange、onFocus、onBlur)都传递给内部的原生input,同时保留你的浮动标签逻辑:

import { useId, useState, type ComponentProps } from "react";
import { cn } from "../../../utils/cn";

type Props = ComponentProps<"input"> & {
  label: string;
};

export function Input({
  className,
  type = "text",
  label,
  value,
  onChange,
  onFocus,
  onBlur,
  ...props
}: Props) {
  const [isFocused, setIsFocused] = useState(false);
  const id = useId();
  // 直接用传入的value判断是否有值,不用自己维护hasValue状态,避免不同步
  const hasValue = value !== undefined && value.trim() !== "";
  const isFloating = isFocused || hasValue;

  const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
    setIsFocused(true);
    // 别忘了调用RHF传入的onFocus,让它追踪焦点状态
    onFocus?.(e);
  };

  const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
    setIsFocused(false);
    // 调用RHF传入的onBlur,触发校验逻辑
    onBlur?.(e);
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 把输入变化同步给RHF,让它更新表单状态
    onChange?.(e);
  };

  return (
    <div className="relative mb-1">
      <label
        htmlFor={id}
        className={`absolute left-3 top-1/2 -translate-y-1/2 transition-all duration-200 peer-focus:-translate-y-4 peer-focus:text-xs peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:text-xs px-1 bg-white`}
      >
        {label}
      </label>
      <input
        id={id}
        type={type}
        className={cn(
          "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent peer",
          className
        )}
        value={value}
        onChange={handleChange}
        onFocus={handleFocus}
        onBlur={handleBlur}
        placeholder=" " // 留空占位符,配合浮动标签
        {...props} // 传递RHF register返回的其他props,比如name、ref等
      />
    </div>
  );
}

2. 确保SignInForm里的Input用法正确

你现在的用法是对的,保持不变就行:

<Input label="Email" {...register("email")} />

3. 复杂场景可选:用Controller组件更稳妥

如果你的Input组件需要更复杂的自定义逻辑,比如自己完全控制value,那建议用RHF的Controller组件来包裹,这样状态同步会更明确:

首先从useForm里解构control:

const { register, formState, handleSubmit, control } = useForm<SignInFormData>({
  mode: "onSubmit",
  defaultValues: { email: "" },
  resolver: zodResolver(signInFormSchema),
});

然后用Controller包裹Input:

<Controller
  name="email"
  control={control}
  render={({ field }) => (
    <Input label="Email" {...field} />
  )}
/>

额外调试小技巧

  1. 提交前先在onSubmit里打个日志,看看提交的data是什么:
async function onSubmit(data: SignInFormData) {
  console.log("提交的邮箱值:", data.email); // 确认是不是你输入的邮箱
  requestOtp(data);
}
  1. 检查formState.isDirty和formState.isValid的值,看看RHF有没有正确识别表单的校验状态。

修改完之后再试试,应该控制台不会再抛未捕获的Zod错误,页面上的错误提示也会正常显示了~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:40:29