React Hook Form结合Zod与Material UI未显示校验错误问题
解决Material UI + React Hook Form + Zod校验错误不显示的问题
问题根源分析
你的代码存在三个核心问题导致错误信息无法正常展示:
- Zod Schema逻辑错误:
z.string().min(10).or(z.string().nonempty('required'))的or逻辑完全不符合需求——只要输入满足「非空」或「长度≥10」其中一项就会通过校验,但你实际需要的是「必填且长度至少10」,or的用法颠倒了逻辑。 - Material UI TextField未绑定错误状态:没有将React Hook Form的错误信息传递给TextField组件,导致组件无法识别校验失败状态,也就不会显示错误样式和文本。
- 表单提交方式不规范:用Button的
onClick触发提交,未利用HTML表单的默认onSubmit事件,可能导致校验流程未被正确触发。
分步修复方案
1. 修正Zod Schema逻辑
将or改为链式调用,先校验必填规则,再校验最小长度,确保错误信息按优先级触发:
// @/utils/validations/AddNewProductFormValidation.ts import { z } from 'zod'; export const productInfoSchema = z.object({ product_name: z.string() .nonempty('产品名称不能为空') // 优先校验必填 .min(10, '产品名称长度不能少于10个字符'), // 再校验长度 }); export type productInfoSchemaType = z.infer<typeof productInfoSchema>;
2. 给TextField绑定错误状态
在Controller的render函数中,通过fieldState获取当前字段的错误信息,传递给TextField的error和helperText属性,让Material UI组件自动渲染错误样式:
<Controller control={control} name='product_name' render={({ field, fieldState }) => ( <TextField {...field} inputRef={field.ref} label='Product Name' error={!!fieldState.error} // 有错误时显示红色边框 helperText={fieldState.error?.message} // 显示Zod定义的错误文本 fullWidth // 可选:让输入框占满容器宽度 /> )} />
3. 规范表单提交方式
将form的onSubmit事件绑定为handleSubmit(onSubmit),并将Button的type设为submit,遵循HTML表单的默认行为:
<form onSubmit={handleSubmit(onSubmit)}> {/* 表单元素 */} <div className='justify-end'> <Button type="submit" variant='contained'> Next </Button> </div> </form>
完整修复后的代码
ProductInfoForm Page
'use client'; import React from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { productInfoSchema, productInfoSchemaType, } from '@/utils/validations/AddNewProductFormValidation'; import { Button, TextField } from '@mui/material'; import { zodResolver } from '@hookform/resolvers/zod'; const ProductInfoStep = () => { const { handleSubmit, formState: { errors }, control } = useForm<productInfoSchemaType>({ mode: 'all', reValidateMode: 'onChange', resolver: zodResolver(productInfoSchema), }); const onSubmit: SubmitHandler<productInfoSchemaType> = (data) => { console.log(data); }; return ( <div className='p-5 flex flex-col gap-5'> <form onSubmit={handleSubmit(onSubmit)}> <Controller control={control} name='product_name' render={({ field, fieldState }) => ( <TextField {...field} inputRef={field.ref} label='Product Name' error={!!fieldState.error} helperText={fieldState.error?.message} fullWidth /> )} /> <div className='justify-end mt-4'> <Button type="submit" variant='contained'> Next </Button> </div> {/* 可选:额外显示独立的错误文本 */} <div className='mt-2 text-red-500'> {errors.product_name && <p>{errors.product_name.message}</p>} </div> </form> </div> ); }; export default ProductInfoStep;
Zod Schema
import { z } from 'zod'; export const productInfoSchema = z.object({ product_name: z.string() .nonempty('产品名称不能为空') .min(10, '产品名称长度不能少于10个字符'), }); export type productInfoSchemaType = z.infer<typeof productInfoSchema>;
额外说明
- 使用
fieldState.error比直接读取formState.errors.product_name更精准,因为Controller会自动管理当前字段的实时状态。 mode: 'all'会在输入变化、表单提交、输入框失去焦点时都触发校验,若需调整校验时机,可修改mode参数(如onSubmit仅在提交时校验)。
内容的提问来源于stack exchange,提问作者Faraz Ali
相关产品推荐
相关产品推荐

