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

React JS中嵌套表单字段验证实现方案求助

解决方案:Reactstrap嵌套表单验证(不修改原API数据)

嘿,我完全理解你的困境——不能改动来自API的data对象,还要给每个嵌套的表单字段单独加必填和邮箱正则验证,确实有点绕。不过咱们可以通过组件状态单独管理表单输入和验证状态来解决,完全不碰原数据。下面是完整的可运行方案:

const { Component, Fragment } = React;
const { Button, Collapse, Form, FormGroup, Input, FormFeedback } = Reactstrap;

// 邮箱正则验证规则
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      formData: [], // 存储每个sublevel项的输入值
      errors: []     // 存储每个字段的错误信息
    };
  }

  componentDidMount() {
    // 根据原data结构初始化formData和errors,完全不修改原data
    const initialFormData = data.map(levelOne => ({
      sublevel: levelOne.sublevel.map(subItem => ({
        ccoId: subItem.ccoId,
        email: subItem.email,
        firstName: subItem.firstName,
        lastName: subItem.lastName
      }))
    }));

    const initialErrors = data.map(levelOne => ({
      sublevel: levelOne.sublevel.map(() => ({
        ccoId: '',
        email: '',
        firstName: '',
        lastName: ''
      }))
    }));

    this.setState({ formData: initialFormData, errors: initialErrors });
  }

  // 验证单个字段
  validateField(value, fieldName, i, j) {
    let error = '';
    // 必填校验
    if (!value.trim()) {
      error = `${fieldName} 是必填项`;
    }
    // 邮箱额外正则校验
    else if (fieldName === 'email' && !EMAIL_REGEX.test(value)) {
      error = '请输入有效的邮箱地址';
    }

    // 更新对应字段的错误状态
    this.setState(prevState => {
      const newErrors = [...prevState.errors];
      newErrors[i].sublevel[j][fieldName] = error;
      return { errors: newErrors };
    });

    return error === ''; // 返回是否验证通过
  }

  // 处理输入变化
  handleChange = (i, j, e) => {
    const { name, value } = e.target;
    // 更新输入值
    this.setState(prevState => {
      const newFormData = [...prevState.formData];
      newFormData[i].sublevel[j][name] = value;
      return { formData: newFormData };
    }, () => {
      // 输入更新后触发验证
      this.validateField(value, name, i, j);
    });
  }

  // 检查所有字段是否验证通过
  isFormValid() {
    return this.state.errors.every(levelOne => 
      levelOne.sublevel.every(subItem => 
        Object.values(subItem).every(error => error === '')
      )
    );
  }

  // 提交表单(示例)
  handleSubmit = () => {
    if (this.isFormValid()) {
      console.log('表单验证通过,提交数据:', this.state.formData);
      // 这里可以调用API提交表单数据
    }
  }

  render() {
    const { formData, errors } = this.state;
    return (
      <div className="container mt-4">
        {data.map((levelOne, i) => (
          <div key={i} className="mb-4">
            <h4>{levelOne.title}</h4>
            {levelOne.sublevel.map((subLevel, j) => (
              <div key={j} className="border p-3 mb-2">
                <Form inline>
                  <FormGroup className="mr-3">
                    <Input
                      name="ccoId"
                      maxLength="50"
                      value={formData[i]?.sublevel[j]?.ccoId || ''}
                      placeholder="ccoId"
                      onChange={(e) => this.handleChange(i, j, e)}
                      invalid={!!errors[i]?.sublevel[j]?.ccoId}
                    />
                    <FormFeedback>{errors[i]?.sublevel[j]?.ccoId}</FormFeedback>
                  </FormGroup>

                  <FormGroup className="mr-3">
                    <Input
                      name="email"
                      type="email"
                      value={formData[i]?.sublevel[j]?.email || ''}
                      placeholder="Email"
                      onChange={(e) => this.handleChange(i, j, e)}
                      invalid={!!errors[i]?.sublevel[j]?.email}
                    />
                    <FormFeedback>{errors[i]?.sublevel[j]?.email}</FormFeedback>
                  </FormGroup>

                  <FormGroup className="mr-3">
                    <Input
                      name="firstName"
                      maxLength="50"
                      value={formData[i]?.sublevel[j]?.firstName || ''}
                      placeholder="firstName"
                      onChange={(e) => this.handleChange(i, j, e)}
                      invalid={!!errors[i]?.sublevel[j]?.firstName}
                    />
                    <FormFeedback>{errors[i]?.sublevel[j]?.firstName}</FormFeedback>
                  </FormGroup>

                  <FormGroup>
                    <Input
                      name="lastName"
                      maxLength="50"
                      value={formData[i]?.sublevel[j]?.lastName || ''}
                      placeholder="lastName"
                      onChange={(e) => this.handleChange(i, j, e)}
                      invalid={!!errors[i]?.sublevel[j]?.lastName}
                    />
                    <FormFeedback>{errors[i]?.sublevel[j]?.lastName}</FormFeedback>
                  </FormGroup>
                </Form>
              </div>
            ))}
          </div>
        ))}
        <Button 
          color="secondary" 
          size="lg" 
          onClick={this.handleSubmit}
          disabled={!this.isFormValid()}
        >
          Submit
        </Button>
      </div>
    );
  }
}

// 原API数据(不可修改)
const data = [ { "Id":22383, "title":"Title 1", "quantity":5, "price":12, "sublevel":[ { "confirm":3, "status":0, "email":"test12@gmail.com", "ccoId":"test12", "firstName":"test", "lastName":"user", "partId":22383 }, { "confirm":3, "status":0, "email":"", "ccoId":"", "firstName":"", "lastName":"", "partId":22383 }, { "confirm":3, "status":0, "email":"", "ccoId":"", "firstName":"", "lastName":"", "partId":22383 }, { "confirm":3, "status":0, "email":"", "ccoId":"", "firstName":"", "lastName":"", "partId":22383 }, { "confirm":3, "status":0, "email":"", "ccoId":"", "firstName":"", "lastName":"", "partId":22383 } ] }, { "Id":22383, "title":"Title 2", "quantity":1, "price":5, "sublevel":[ { "confirm":3, "status":0, "email":"test23@gmail.com", "ccoId":"test23", "firstName":"hello", "lastName":"world", "partId":22383 } ] } ]

ReactDOM.render(<App />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>

关键思路解析:

  • 状态分离:用formData存储用户输入的最新值,errors存储每个字段的验证错误,两者结构完全对应原data,但完全独立,不会修改原API数据。
  • 实时验证:每次输入变化时调用validateField函数,实时更新错误状态,并通过reactstrap的FormFeedback显示错误提示。
  • 精准定位:通过一级索引i和二级索引j,精准找到每个嵌套字段对应的状态,确保验证和输入更新的准确性。
  • 整体校验:isFormValid函数遍历所有错误状态,只有当所有字段都验证通过时,Submit按钮才会启用。

这样既满足了不能修改原data的要求,又完美实现了每个字段的必填验证和邮箱正则验证,而且和reactstrap的表单组件无缝集成~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 10:17:36