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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 12:37:56