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

React动态渲染MaterialUI表单:手机号校验行级独立提示需求

解决动态表单中手机号校验仅针对当前行的问题

我来帮你搞定这个问题!你遇到的核心痛点是没有为每一行表单单独维护错误状态,导致校验时所有手机号字段都会同步显示错误。我们只需要给每个成员对象扩展错误相关的状态字段,就能实现仅当前输入行显示错误提示的效果。

步骤1:扩展表单数据的状态结构

首先,修改groupDetails的初始状态,为每个成员添加手机号的错误状态和提示文本字段:

const [groupDetails, setGroupDetails] = useState([ 
  { fullName: "", phoneNo: "", gender: "", phoneError: false, phoneHelperText: "" }, 
]);

步骤2:更新输入处理函数,添加行内校验逻辑

修改handleChange函数,在处理手机号输入时,针对当前行单独做校验,并更新对应行的错误状态:

function handleChange(event, index) {
  const { name, value } = event.target;
  // 先解构数组中的当前项,避免直接修改原状态对象
  let newArr = [...groupDetails]; 
  let item = {...newArr[index]};

  // 仅对手机号字段做校验
  if (name === "phoneNo") {
    // 这里可以自定义你的校验规则,示例为中国大陆11位数字手机号
    const validPhoneRegex = /^1[3-9]\d{9}$/;
    if (!validPhoneRegex.test(value.trim())) {
      item.phoneError = true;
      item.phoneHelperText = "请输入有效的11位手机号码";
    } else {
      item.phoneError = false;
      item.phoneHelperText = "";
    }
  }

  // 更新当前字段的值
  item[name] = value;
  newArr[index] = item;
  setGroupDetails(newArr);
}

步骤3:绑定错误状态到对应TextField

修改手机号输入框的渲染代码,将error和helperText属性绑定到当前行的错误状态:

<TextField 
  id={`phoneNo${i + 1}`} 
  name="phoneNo" 
  variant="outlined" 
  margin="none" 
  label="Mobile Number" 
  onChange={(event) => handleChange(event, i)}
  error={groupDetails[i]?.phoneError}
  helperText={groupDetails[i]?.phoneHelperText}
/>

步骤4:同步表单行数与输入的成员数量(可选但重要)

为了避免索引越界问题,添加useEffect监听groupMembersCount的变化,自动同步groupDetails数组的长度:

import { useEffect } from 'react'; // 别忘了导入useEffect

// ...其他代码

useEffect(() => {
  const targetCount = parseInt(groupMembersCount) || 0;
  const currentCount = groupDetails.length;

  if (currentCount < targetCount) {
    // 添加新的成员对象
    const newMembers = Array(targetCount - currentCount).fill(null).map(() => ({
      fullName: "", 
      phoneNo: "", 
      gender: "", 
      phoneError: false, 
      phoneHelperText: "" 
    }));
    setGroupDetails(prev => [...prev, ...newMembers]);
  } else if (currentCount > targetCount) {
    // 截断数组到目标长度
    setGroupDetails(prev => prev.slice(0, targetCount));
  }
}, [groupMembersCount]);

完整代码示例

整合以上修改后的完整组件代码:

import React, { useState, useEffect } from 'react';
import { TextField, Select, MenuItem, Button, Card, CardContent, Typography } from '@mui/material';

export default function DynamicGroupMember() {
  const [groupMembersCount, setGroupMembersCount] = useState(0);
  const [show, setShow] = useState(false);
  const [groupDetails, setGroupDetails] = useState([ 
    { fullName: "", phoneNo: "", gender: "", phoneError: false, phoneHelperText: "" }, 
  ]);

  useEffect(() => {
    const targetCount = parseInt(groupMembersCount) || 0;
    const currentCount = groupDetails.length;

    if (currentCount < targetCount) {
      const newMembers = Array(targetCount - currentCount).fill(null).map(() => ({
        fullName: "", 
        phoneNo: "", 
        gender: "", 
        phoneError: false, 
        phoneHelperText: "" 
      }));
      setGroupDetails(prev => [...prev, ...newMembers]);
    } else if (currentCount > targetCount) {
      setGroupDetails(prev => prev.slice(0, targetCount));
    }
  }, [groupMembersCount]);

  function handleChange(event, index) {
    const { name, value } = event.target;
    let newArr = [...groupDetails]; 
    let item = {...newArr[index]};

    if (name === "phoneNo") {
      const validPhoneRegex = /^1[3-9]\d{9}$/;
      if (!validPhoneRegex.test(value.trim())) {
        item.phoneError = true;
        item.phoneHelperText = "请输入有效的11位手机号码";
      } else {
        item.phoneError = false;
        item.phoneHelperText = "";
      }
    }

    item[name] = value;
    newArr[index] = item;
    setGroupDetails(newArr);
  }

  return (
    <div>
      Number of Group: 
      <TextField 
        name="groupMembersCount" 
        type="number" // 限制输入为数字
        onChange={(event) => setGroupMembersCount(event.target.value)}
        inputProps={{ min: 0 }} // 禁止输入负数
      />
      {Array.from({length: parseInt(groupMembersCount) || 0}).map( (e, i) => (
        <div key={i}>
          <strong>Member #{i + 1}</strong>
          <div className="getIndex" name={i + 1}>
            <TextField 
              id={`name${i + 1}`} 
              name="fullName" 
              variant="outlined" 
              margin="none" 
              label="Name" 
              onChange={(event) => handleChange(event, i)} 
              value={groupDetails[i]?.fullName} // 添加value绑定,避免受控组件警告
            />
            <TextField 
              id={`phoneNo${i + 1}`} 
              name="phoneNo" 
              variant="outlined" 
              margin="none" 
              label="Mobile Number" 
              onChange={(event) => handleChange(event, i)}
              error={groupDetails[i]?.phoneError}
              helperText={groupDetails[i]?.phoneHelperText}
              value={groupDetails[i]?.phoneNo} // 添加value绑定
            />
            <Select 
              id={`gender${i + 1}`} 
              name="gender" 
              variant="outlined" 
              margin="none" 
              label="Gender" 
              onChange={(event) => handleChange(event, i)}
              value={groupDetails[i]?.gender} // 添加value绑定
            >
              <MenuItem value="MALE">Male</MenuItem>
              <MenuItem value="FEMALE">Female</MenuItem>
              <MenuItem value="OTHER">Other</MenuItem>
            </Select>
          </div>
        </div>
      ) )}
      <Button onClick={() => setShow(true)}>Show</Button>
      { show ? groupDetails.map((member, idx) => (
        <Card key={idx}>
          <CardContent>
            <Typography color="textSecondary" gutterBottom>
              {member.fullName}
            </Typography>
            <Typography variant="h5" component="h2">
              {member.phoneNo}
            </Typography>
            <Typography color="textSecondary">
              {member.gender}
            </Typography>
          </CardContent>
        </Card>
      )) : null }
    </div>
  );
}

额外优化说明

  • 给所有表单控件添加了value绑定,避免React受控组件的警告;
  • 给成员数量输入框设置了type="number"和inputProps={{ min: 0 }},限制输入为非负整数;
  • 用Array.from替代了Array.apply,写法更简洁现代。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 20:37:28