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

如何在React+Formik中使用Yup异步验证并避免先显示必填错误

解决Formik+Yup条码表单异步验证时必填错误误显示问题

问题描述

我使用Formik和Yup构建React表单,实现条码输入列表功能,需通过API异步验证条码是否存在。当前代码运行时出现异常:添加多个输入框并输入内容后,会先显示“Barcode is required”的必填错误提示,但此时输入框已有内容。我希望仅在输入框为空时显示必填提示,输入内容后只展示异步验证的结果。

现有代码

import { Formik, Field, FieldArray, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

interface BarcodeFormValues {
  barcodes: { code: string }[];
}

const validationSchema = Yup.object({
  barcodes: Yup.array().of(
    Yup.object().shape({
      code: Yup.string()
        .required("Barcode is required")
        .test("checkBarcodeExists", "Barcode does not exist", async (value) => {
          if (!value) return false;

          // 模拟API延迟
          await new Promise((resolve) => setTimeout(resolve, 2000));

          // 模拟验证失败
          return false;
        }),
    })
  ),
});

const initialValues: BarcodeFormValues = {
  barcodes: [{ code: "" }],
};

const BarcodeForm = () => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      validateOnChange={true}
      validateOnBlur={false}
      onSubmit={(values) => {
        console.log("Submitted values:", values);
      }}
    >
      {({ values }) => (
        <Form>
          <FieldArray name="barcodes">
            {({ remove, push }) => (
              <div>
                {values.barcodes.length > 0 &&
                  values.barcodes.map((barcode, index) => (
                    <div key={index}>
                      <Field
                        name={`barcodes.${index}.code`}
                        placeholder="Enter barcode"
                      />
                      <ErrorMessage
                        name={`barcodes.${index}.code`}
                        component="div"
                      />
                      <button type="button" onClick={() => remove(index)}>
                        Delete
                      </button>
                    </div>
                  ))}
                <button type="button" onClick={() => push({ code: "" })}>
                  Add New
                </button>
              </div>
            )}
          </FieldArray>
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  );
};

export default BarcodeForm;

解决方案

1. 调整Yup验证逻辑,分离必填与异步验证

核心问题在于原有异步验证逻辑中,空值时返回false,导致必填验证和异步验证的错误状态冲突。修改验证规则,让异步验证仅在值非空时执行,空值情况完全由必填验证处理:

const validationSchema = Yup.object({
  barcodes: Yup.array().of(
    Yup.object().shape({
      code: Yup.string()
        .required("Barcode is required")
        .test(
          "checkBarcodeExists",
          "Barcode does not exist",
          async (value) => {
            // 空值时直接返回true,交由必填验证处理错误
            if (!value) return true;
            
            // 模拟API异步验证
            await new Promise((resolve) => setTimeout(resolve, 2000));
            // 替换为实际API请求逻辑,例如:
            // const res = await fetch(`/api/validate-barcode?code=${value}`);
            // return res.ok;
            
            // 测试用返回false
            return false;
          }
        ),
    })
  ),
});

2. 优化错误显示逻辑(可选)

如果仍存在错误显示延迟,可通过ErrorMessage的自定义渲染逻辑,强制过滤掉非空值时的必填错误:

<ErrorMessage
  name={`barcodes.${index}.code`}
  render={(msg) => {
    const currentValue = values.barcodes[index].code;
    // 若输入框有值,且错误为必填提示,则不显示
    if (currentValue && msg === "Barcode is required") {
      return null;
    }
    return <div>{msg}</div>;
  }}
/>

3. 添加异步验证防抖(可选)

频繁输入会触发大量异步请求,可通过防抖减少请求次数:

先安装依赖:

npm install lodash.debounce

修改Formik配置:

import debounce from 'lodash.debounce';
import { useMemo } from 'react';

// ...

const BarcodeForm = () => {
  // 创建防抖验证函数
  const debouncedValidate = useMemo(() => 
    debounce(async (values: BarcodeFormValues) => {
      try {
        await validationSchema.validate(values, { abortEarly: false });
        return {};
      } catch (err) {
        return Yup.errorToFormErrors(err as Yup.ValidationError);
      }
    }, 500), 
  []);

  return (
    <Formik
      initialValues={initialValues}
      validate={debouncedValidate}
      validateOnChange={true}
      validateOnBlur={false}
      onSubmit={(values) => {
        console.log("Submitted values:", values);
      }}
    >
      {/* 原有表单内容 */}
    </Formik>
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:33:14