FieldArray场景下如何为元素唯一性Yup校验配置正确错误提示?
FieldArray 邮箱校验的错误展示修复方案
问题根源
- 你配置的唯一性校验属于数组根级别校验,校验失败时,错误信息默认会挂载在整个数组字段(对应路径
${name})下 - 循环内的
ErrorMessage组件绑定的是单个数组元素的路径${name}.${i},当当前元素没有触发自身的格式校验错误时,Formik的错误解析逻辑会沿着路径层级向上查找,最终拿到数组根路径上的字符串形式的错误提示 - 由于组件会按索引
i对获取到的内容做取值操作,字符串是可按索引访问的类数组结构,最终就出现了每个循环位置逐字拆分显示错误提示的异常效果
可落地方案
方案1:分层渲染错误(改动量最小)
不需要修改现有Yup校验逻辑,只要调整渲染结构,把数组级错误和元素级错误的渲染位置完全分开,避免跨路径取值:
- 循环内部保留原有的单元素错误展示逻辑,专门负责渲染单个邮箱的格式校验提示
- 在循环结构的外部、数组字段的容器位置,单独增加一个绑定了数组根路径的
ErrorMessage组件,专门渲染唯一性校验的提示
调整后的渲染代码如下:
render={({ form, push, remove }) => ( <div> {/* 单独渲染数组根级的唯一性错误,放在循环外侧 */} <ErrorMessage name={name} render={msg => ( <FormFeedback style={{ display: "block" }}> {msg} </FormFeedback> )} /> {/* 原有循环逻辑保持不变 */} {range(0, form.values[name].length).map(i => ( <div className={styles.inputContainer} key={i}> <div> <InputComponent name={`${name}.${i}`} {...props} /> <Button color="danger" onClick={() => remove(i)}> <FaTimes /> </Button> </div> {/* 循环内的ErrorMessage仅负责渲染单元素的格式错误 */} <ErrorMessage name={`${name}.${i}`} render={msg => ( <FormFeedback style={{ display: "block" }}> {msg} </FormFeedback> )} /> </div> ))} <Button color="info" onClick={() => push("")} disabled={disabled || form.values[name].slice(-1)[0] === ""} > <FaPlus /> </Button> </div> )}
该方案适配成本最低,完全兼容现有校验规则,只要明确划分两类错误的渲染边界,就能彻底解决逐字拆分显示的问题。缺点是唯一性提示统一展示在数组区域顶部,无法精准定位到具体重复的输入框位置。
方案2:调整Yup校验逻辑,实现精准错误定位
如果需要把唯一性校验的提示精准展示在重复项的输入框下方,只需要调整原有Yup规则的错误挂载逻辑,不要把错误挂在数组根路径,而是通过createError把错误绑定到具体重复元素的对应路径上,让循环内的错误组件可以正常读取到对应内容。
调整后的Yup校验规则:
export const emails = yup .array( yup .string() .matches(/^\S+@\S+$/, { message: "Must be a valid email id" }) ) .test("email-unique", "Email ids need to be unique", function(values) { // 过滤空值,空值的格式校验由子项规则处理,不参与唯一性判断 const nonEmptyValues = values.filter(Boolean); // 无重复值时直接通过校验 if (new Set(nonEmptyValues).size === nonEmptyValues.length) return true; // 统计每个邮箱值对应的所有索引位置 const valueIndexMap = {}; values.forEach((value, index) => { if (!value) return; if (!valueIndexMap[value]) valueIndexMap[value] = []; valueIndexMap[value].push(index); }); // 收集所有重复值对应的索引 const duplicatedIndexes = Object.values(valueIndexMap) .filter(indexList => indexList.length > 1) .flat(); // 将错误精准挂载到对应重复元素的路径上,而非数组根路径 return this.createError({ path: `${this.path}.${duplicatedIndexes[0]}`, message: "Email ids need to be unique" }) });
调整后,唯一性错误会直接挂载到对应元素的路径下,循环内原有的错误展示逻辑不需要做任何修改,就可以同时兼容单元素格式校验、数组级唯一性校验的错误展示,还能把错误提示精准定位到重复的输入框位置。
注意:test方法的回调必须使用普通函数,不能使用箭头函数,否则无法获取Yup的校验上下文,会导致createError等方法调用失败。
内容的提问来源于stack exchange,提问作者Dragon
相关产品推荐
相关产品推荐

