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

React表单校验错误信息应用至所有表单项问题排查

表单错误提示展示异常截图

问题根源

错误提示在所有表单项上展示是状态设计逻辑错误导致的:

  • 你将错误标记hasError、错误提示文案errorMessage定义在页面组件顶层,属于整个表单共用的单一全局状态
  • 遍历渲染所有TextboxScannerElement组件时,你给每一个输入项都传入了同一个全局errorMessage,只要任意一个输入项校验失败触发全局状态更新,所有表单项都会拿到错误文案并展示
  • 附带逻辑bug:你把清空错误的逻辑写在了isCodeExists请求函数中,每次触发校验发请求时都会直接清空全局错误,会覆盖其他表单项已有的校验失败状态。

修复方案

取消全局单一错误状态,将错误状态下沉绑定到每个独立的表单项数据上,每个输入项维护自己的校验状态:

  • 移除页面顶层的全局hasError、errorMessage状态
  • 初始化表单数据时,给每个表单项增加独立的hasError、errorMessage字段
  • 输入触发校验时,只更新当前操作项的错误状态,不做全局修改
  • 渲染表单项时,只传入当前项自身的错误信息
  • 把清空错误的逻辑移到输入触发的时机,不要放在公共请求函数里
  • 提交前增加全量校验,存在校验失败项时阻止提交

修复后的完整代码如下:

import React, { useState, useEffect } from 'react';
import TextboxScannerElement from './TextboxScannerElement';

export default function CollectionContainerPage(props) {
  const [pageUserInput, setPageUserInput] = useState([]);

  useEffect(() => {
    if (props.pageData !== undefined) {
      // 初始化时给每个表单项绑定独立的错误状态字段
      const initFormData = props.pageData.map(item => ({
        ...item,
        hasError: false,
        errorMessage: ''
      }))
      setPageUserInput(initFormData);
    }
  }, [props.pageData]);

  async function onUserInput(element, value) {
    if (typeof element === 'string') {
      let pageUserInputCopy = [...pageUserInput];
      const targetIndex = pageUserInputCopy.findIndex((obj) => obj.key === element);
      let targetItem = { ...pageUserInputCopy[targetIndex] };
      targetItem.userInput = value;
      // 输入时先清空当前项的历史错误
      targetItem.hasError = false;
      targetItem.errorMessage = '';
      pageUserInputCopy[targetIndex] = targetItem;
      setPageUserInput(pageUserInputCopy);

      const isInputValid = await isValid(value);
      if (!isInputValid) {
        // 仅更新当前校验失败项的错误状态
        const updatedList = [...pageUserInputCopy];
        const errItemIndex = updatedList.findIndex((obj) => obj.key === element);
        updatedList[errItemIndex] = {
          ...updatedList[errItemIndex],
          hasError: true,
          errorMessage: "Collection Container ID already exists!"
        }
        setPageUserInput(updatedList);
      }
    }
  }

  function isCodeExists(userInput) {
    const code = encodeURIComponent(userInput);
    const testURL =
      loris.BaseURL +
      '/biosample/ajax/validateScannableCode.php' +
      '?scannable_code=' +
      code;

    return fetch(
      testURL,
      {
        credentials: 'include',
        method: 'GET',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      })
      .then((response) => response.json())
      .then((responseData) => {
        return responseData === 2 ? 2 : 1;
      })
      .catch(error => console.warn(error));
  }

  async function isValid(userInput) {
    const checkResult = await isCodeExists(userInput);
    return checkResult !== 2;
  }

  function handleSubmit(e) {
    // 提交前全量校验,存在错误项阻止提交
    const hasInvalidItem = pageUserInput.some(item => item.hasError);
    if (hasInvalidItem) return;
    props.updatePageData(pageUserInput);
    props.nextStep();
  }

  function previous() {
    props.updatePageData(pageUserInput);
    props.prevStep();
  }

  const form =
    pageUserInput.length === 0
      ? null
      : pageUserInput.map(({ label, key, userInput, hasError, errorMessage }) => (
          <TextboxScannerElement
            keyStr={key}
            label={label}
            name={key}
            onUserInput={onUserInput}
            value={userInput}
            hasError={hasError}
            // 仅传入当前项自身的错误信息
            errorMessage={errorMessage}
          />
        ));

  return (
    <>
      <div className="instructions">
        <strong>Instructions:</strong>
        <p>Scan the Collection Containers found in the kit.</p>
      </div>
      <FormElement
        className="FormElement"
        name="form"
        fileUpload={false}
        onSubmit={handleSubmit}
        onUserInput={onUserInput}
      >
        {form}
        <ButtonElement
          label="Next"
          onUserInput={onUserInput}
        />
      </FormElement>
      <button
        onClick={previous}
        type="button"
        className="btn btn-primary prevButton"
      >
        Back
      </button>
    </>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:15:40