React-Hook-Form中Field Array自定义Refine校验的错误状态无法更新问题
React-Hook-Form中Field Array自定义Refine校验的错误状态无法更新问题
兄弟,我之前也踩过这个坑!咱们先理清楚问题出在哪,再一步步解决~
首先你的自定义校验逻辑(要求至少一个URL标记为主站)确实在触发,从你console.log能输出就能看出来,但为啥错误消息没更新到表单状态里?核心原因是:你给整个urls数组加的顶层Refine校验,错误会被存在表单错误的顶层errors.urls里,而React Hook Form不会自动把这个错误关联到单个field array元素上,也不会自动帮你展示出来。
下面给你几个具体的解决方案,亲测有效:
方案一:手动读取并展示顶层数组的校验错误
既然错误存在errors.urls里,那我们直接在组件里获取它然后渲染就行。比如在你的Form.tsx里:
import { useFormContext } from "react-hook-form"; import Typography from "@mui/material/Typography"; // 在组件内部获取表单状态 const { formState: { errors } } = useFormContext(); // 在合适的位置(比如field array的上方/下方)展示错误 {errors.urls && ( <Typography variant="body2" color="error" sx={{ mb: 2 }}> {errors.urls.message} </Typography> )}
方案二:确保校验时机正确触发
有时候字段变化时,顶层数组的校验没被重新触发,你可以在useForm的配置里调整校验模式,确保每次字段变更都跑一遍全量校验:
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { schema } from "./你的Schema文件路径"; const form = useForm({ resolver: zodResolver(schema), mode: "onChange", // 每次字段变化就触发校验,也可以选onBlur按需调整 defaultValues: { urls: [{ path: "", main: false, id: "init-id-1" }] } });
完整的Form.tsx示例(结合以上方案)
把这些点整合到你的组件里,大概是这样:
import { Controller, useFieldArray, useFormContext } from "react-hook-form"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import RemoveCircleTwoToneIcon from "@mui/icons-material/RemoveCircleTwoTone"; import AddCircleTwoToneIcon from "@mui/icons-material/AddCircleTwoTone"; import TextField from "@mui/material/TextField"; import Checkbox from "@mui/material/Checkbox"; import FormControlLabel from "@mui/material/FormControlLabel"; export default function UrlsFieldArray() { const { control, formState: { errors } } = useFormContext(); const { fields, append, remove } = useFieldArray({ control, name: "urls" }); return ( <Stack spacing={2}> {/* 展示数组顶层的自定义校验错误 */} {errors.urls && ( <Typography variant="body2" color="error"> {errors.urls.message} </Typography> )} {fields.map((item, index) => ( <Stack key={item.id} direction="row" spacing={2} alignItems="center"> <Controller control={control} name={`urls.${index}.path`} render={({ field }) => ( <TextField {...field} label="URL地址" error={!!errors?.urls?.[index]?.path} helperText={errors?.urls?.[index]?.path?.message} /> )} /> <Controller control={control} name={`urls.${index}.main`} render={({ field }) => ( <FormControlLabel control={<Checkbox {...field} checked={field.value} />} label="设为主站" /> )} /> {fields.length > 1 && ( <RemoveCircleTwoToneIcon onClick={() => remove(index)} style={{ cursor: "pointer", color: "red" }} /> )} </Stack> ))} {fields.length < 3 && ( <AddCircleTwoToneIcon onClick={() => append({ path: "", main: false, id: Date.now().toString() })} style={{ cursor: "pointer", color: "green" }} /> )} </Stack> ); }
补充说明
为啥min/max能正常工作?因为Zod的min()/max()属于数组的内置规则,React Hook Form会自动把这些错误关联到数组字段上,而自定义的refine是你自己加的顶层校验,错误不会自动映射到单个元素,所以必须手动读取顶层错误来展示。
备注:内容来源于stack exchange,提问作者SageJustus
相关产品推荐
相关产品推荐

