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

NextJS中react-hook-form+Yup验证合法字段仍触发错误

问题:NextJS中react-hook-form结合Yup验证嵌套字段时,已填值仍提示必填错误

在NextJS项目中使用react-hook-form配合Yup做表单验证,表单包含clientInformation下的嵌套字段(如name.firstName、email),输入符合要求的数据后,依然收到"Email is required"、"First name is required"等必填项错误提示。

简化后的表单组件代码

"use client";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import validationSchema from "@/app/lib/validationSchema.js";
import { useEffect } from "react";
import { initialState } from "@/app/lib/initialState";

export default function ClientInformation() {
  const {
    register,
    handleSubmit,
    watch,
    control,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(validationSchema),
    defaultValues: initialState,
  });

  const allFormValues = watch();

  const onSubmit = (data) => {
    console.log(data);
  };

  useEffect(() => {
    console.log("Current Form Values:", allFormValues);
    console.log("Current Form Errors:", errors);
  }, [allFormValues, errors]);

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="space-y-2 divide-y divide-gray-900/10"
    >
      <div className="grid grid-cols-1 gap-x-2 gap-y-8 pt-2 md:grid-cols-3">
        <div className="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
          <div className="px-8 py-2 sm:p-8">
            <div className="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-3">
              <div className="sm:col-span-3">
                <label
                  htmlFor="first-name"
                  className="block text-sm font-medium leading-6 text-gray-900"
                >
                  First name
                </label>
                <div className="mt-2">
                  <input
                    type="text"
                    name="clientInformation.name.firstName"
                    id="first-name"
                    {...register("clientInformation.name.firstName")}
                    autoComplete="given-name"
                    className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  />
                  {errors.clientInformation?.name?.firstName && (
                    <p className="mt-2 text-sm text-red-600">
                      {errors.clientInformation.name.firstName.message}
                    </p>
                  )}
                </div>
              </div>

              <div className="sm:col-span-4">
                <label
                  htmlFor="email"
                  className="block text-sm font-medium leading-6 text-gray-900"
                >
                  Email address
                </label>
                <div className="mt-2">
                  <input
                    id="email"
                    name="clientInformation.email"
                    type="email"
                    {...register("clientInformation.email")}
                    autoComplete="email"
                    className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  />
                  {errors.clientInformation?.email && (
                    <p className="mt-2 text-sm text-red-600">
                      {errors.clientInformation.email.message}
                    </p>
                  )}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className="flex justify-end p-6">
        <button
          type="submit"
          className="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
        >
          Submit
        </button>
      </div>
    </form>
  );
}

验证Schema(validationSchema.js)

import * as Yup from "yup";

const validationSchema = Yup.object().shape({
  "clientInformation.name.firstName": Yup.string()
    .trim()
    .min(2, "First name must be at least 2 characters")
    .max(30, "First name must be less than 50 characters")
    .required("First name is required"),

  "clientInformation.email": Yup.string()
    .email("Invalid email")
    .required("Email is required"),
});

控制台输出

错误输出

{
  "clientInformation": {
    "email": { "message": "Email is required", "type": "optionality", "ref": "undefined" },
    "name": {
      "firstName": { "message": "First name is required", "type": "optionality", "ref": "undefined" }
    }
  }
}

表单值输出

{
  "clientInformation": {
    "email": "gg@gg.com",
    "name": { "firstName": "gggg" }
    // 其他字段...
  }
}
解决方案
  • 修正Yup Schema的嵌套结构定义
    原Schema使用字符串路径定义嵌套字段,易导致react-hook-form与Yup的解析匹配异常,改为嵌套对象方式定义:

    import * as Yup from "yup";
    
    const validationSchema = Yup.object().shape({
      clientInformation: Yup.object().shape({
        name: Yup.object().shape({
          firstName: Yup.string()
            .trim()
            .min(2, "First name must be at least 2 characters")
            .max(30, "First name must be less than 50 characters")
            .required("First name is required"),
        }),
        email: Yup.string()
          .email("Invalid email")
          .required("Email is required"),
      }),
    });
    
  • 确保initialState结构完整
    确认initialState中嵌套对象都被正确初始化,避免undefined引发验证问题:

    export const initialState = {
      clientInformation: {
        name: {
          firstName: "",
        },
        email: "",
        // 其他字段...
      },
    };
    
  • 显式设置shouldUnregister选项
    在NextJS环境下,显式声明shouldUnregister: false(默认值),避免组件注册行为异常:

    const {
      register,
      handleSubmit,
      watch,
      control,
      formState: { errors },
    } = useForm({
      resolver: yupResolver(validationSchema),
      defaultValues: initialState,
      shouldUnregister: false,
    });
    
  • 标记组件为Client Component
    在表单组件文件顶部添加"use client";,确保组件在客户端运行,因为react-hook-form依赖客户端状态管理:

    "use client";
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:45:04