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
相关产品推荐
相关产品推荐

