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

React Hook Form中Yup验证错误未触发及显示问题求助

登录表单Yup + React-Hook-Form验证无效问题排查与修复

以下是导致验证不触发、Errors对象为空的核心问题及修复方案:

1. 表单字段未正确注册到React Hook Form

你当前仅在useEffect中调用register,但未将register返回的属性(如name、onChange、onBlur)传递给自定义Input组件。React Hook Form需要通过这些注册属性追踪字段值变化,才能触发验证逻辑。同时,手动使用setValue绑定onChange的方式会绕过表单的自动状态管理,导致验证无法感知输入变化。

修复方式:

  • 移除useEffect中的register调用
  • 将register('字段名')返回的属性解构传入Input组件(确保你的Input组件能接收并传递这些属性到内部的<input>元素)

2. Yup条件验证缺少variant上下文

你的authSchema中使用yup.string().when('variant')做条件验证,但variant是组件的状态而非表单字段,Yup无法直接获取这个值,导致条件验证逻辑不生效。

修复方式:

  • 在useForm配置中添加defaultValues,将variant作为表单的内部字段,供Yup条件验证使用

3. 按钮点击绕过表单验证

你给Button组件添加了onClick事件直接调用登录/注册方法,这会跳过handleSubmit的验证流程,直接执行提交逻辑,导致验证永远不触发。

修复方式:

  • 移除Button的onClick属性,仅保留表单的onSubmit绑定handleSubmit
  • 给Button添加type="submit"确保它触发表单提交事件

修改后的完整代码示例

import axios from 'axios'
import { useCallback, useState } from 'react'
import { getSession, signIn } from 'next-auth/react'
import { useForm } from 'react-hook-form'
import Button from '../components/Button'
import * as yup from 'yup'
import { yupResolver } from '@hookform/resolvers/yup'

import Input from '../components/Input'

export async function getServerSideProps(context) {
  const session = await getSession(context)

  if (session) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    }
  }

  return {
    props: {},
  }
}

const authSchema = yup.object().shape({
  name: yup.string().when('variant', {
    is: 'register',
    then: yup
      .string()
      .min(3, 'Name must be at least 3 characters')
      .required('Name is required'),
  }),
  email: yup.string().email('Invalid email').required('Email is required'),
  password: yup
    .string()
    .min(6, 'Password must be at least 6 characters')
    .required('Password is required'),
})

const Auth = () => {
  const [variant, setVariant] = useState('login')
  
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(authSchema),
    defaultValues: {
      variant,
      name: '',
      email: '',
      password: ''
    }
  })

  const toggleVariant = useCallback(() => {
    setVariant((currentVariant) =>
      currentVariant === 'login' ? 'register' : 'login'
    )
  }, [])

  const handleLogin = async (data) => {
    try {
      await signIn('credentials', {
        email: data.email,
        password: data.password,
        redirect: false,
        callbackUrl: '/',
      })
    } catch (error) {
      console.log(error)
    }
  }

  const handleRegister = async (data) => {
    try {
      await axios.post('/api/register', {
        email: data.email,
        name: data.name,
        password: data.password,
      })

      handleLogin(data)
    } catch (error) {
      console.log(error)
    }
  }

  return (
    <div className="h-screen w-full">
      <div className="bg-white w-full h-full lg:bg-opacity-50">
        <nav className="px-12 py-5">
          <img src="/header.svg" className="h-12" alt="Logo" />
        </nav>
        <div className="flex justify-center">
          <form
            className="bg-white px-16 py-16 self-center mt-2 lg:w-2/5 lg:max-w-md rounded-md w-full"
            onSubmit={handleSubmit(
              variant === 'login' ? handleLogin : handleRegister
            )}
          >
            <h2 className="text-zinc-500 text-4xl mb-8 font-semibold">
              {variant === 'login' ? 'Sign in' : 'Register'}
            </h2>
            <div className="flex flex-col gap-4">
              {variant === 'register' && (
                <div>
                  {errors.name && (
                    <p className="text-red-500">{errors.name.message}</p>
                  )}
                  <Input
                    id="name"
                    type="text"
                    label="Name"
                    {...register('name')}
                  />
                </div>
              )}
              {errors.email && <p className="text-red-500">{errors.email.message}</p>}
              <Input
                id="email"
                type="email"
                label="Email Address"
                {...register('email')}
              />
              {errors.password && <p className="text-red-500">{errors.password.message}</p>}
              <Input
                type="password"
                id="password"
                label="Password"
                {...register('password')}
              />
            </div>
            <Button
              primary
              modifier={'w-full mt-16'}
              type="submit"
            >
              {variant === 'login' ? 'Login' : 'Sign up'}
            </Button>
            <p className="text-neutral-500 mt-12">
              {variant === 'login'
                ? 'New to the application?'
                : 'Already have an account?'}
              <span
                onClick={toggleVariant}
                className="text-neutral-500 ml-1 hover:underline cursor-pointer"
              >
                {variant === 'login' ? 'Create an account' : 'Login'}
              </span>
            </p>
          </form>
        </div>
      </div>
    </div>
  )
}

export default Auth

额外注意:确保你的自定义Input组件能正确接收并传递属性到原生<input>,示例如下:

const Input = ({ id, label, ...props }) => {
  return (
    <div className="flex flex-col gap-1">
      <label htmlFor={id} className="text-sm text-zinc-600">{label}</label>
      <input 
        id={id} 
        className="border rounded-md px-3 py-2"
        {...props} 
      />
    </div>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:38:08