React中如何获取map渲染表单的选择值与用户ID提交服务端
问题原因
你之前的代码有两个核心错误,导致拿不到正确值:
- 两个下拉选择框的
onChange回调只返回了选中值、没有把值写入组件状态,选了之后状态根本没更新,自然拿不到选择结果。 - 你用单个对象存储考勤数据,循环里反复调用
setAttendance时React会做状态批处理,最终只会保留最后一次循环的赋值,所以永远只能拿到最后一个用户的数据,还会出现所有行选择同步覆盖的问题。
正确实现方案
用「用户ID作为键」的对象结构统一存储所有用户的考勤状态,每个用户的选择独立存储、互不干扰,步骤如下:
- 调整状态定义,移除没用的单独
firstTime/secondTime状态,改用对象存储全量考勤数据,用户列表加载完成后自动给每个用户初始化默认考勤值 - 给下拉框绑定正确的onChange事件,选择时只更新对应用户ID下的考勤值,不会影响其他用户
- 提交时直接遍历存储好的全量考勤对象,把用户ID和对应考勤值组装好发给后端就行
改完的完整核心代码
export default function MarkAttendance() { const dispatch = useDispatch(); const navigate = useNavigate(); const { users, loading, error } = useSelector((state) => state.userList); const today = new Date().toISOString().substr(0, 10); const { userInfo } = useSelector((state) => state.userLogin); // 替换原来的单个考勤状态,用对象存所有用户的考勤,key是用户id const [attendanceMap, setAttendanceMap] = useState({}); useEffect(() => { if (userInfo && userInfo.isAdmin) { dispatch(listUsers()); } else { navigate("/login"); } }, [dispatch, userInfo, navigate]); // 用户列表加载完成后,初始化所有用户的默认考勤值 useEffect(() => { if (users?.length) { const initialAttendance = {}; users.forEach(user => { initialAttendance[user.id] = { first_time: "present", second_time: "present" } }) setAttendanceMap(initialAttendance) } }, [users]) // 通用的选择变更处理,传入用户id和考勤类型,更新对应值 const handleAttendanceChange = (userId, type, value) => { setAttendanceMap(prev => ({ ...prev, [userId]: { ...prev[userId], [type]: value } })) } const handleSubmit = (e) => { e.preventDefault(); // 遍历所有用户的考勤数据,组装成后端需要的格式提交 Object.keys(attendanceMap).forEach(userId => { const submitData = { id: Number(userId), date: today, ...attendanceMap[userId] } postAttendance(submitData); }) } return ( <section> <div className="row"> <div className="col-md-12 text-dark"> <h3>Mark Attendance</h3> for <h4>{today}</h4>'s meal </div> </div> {loading ? ( <Loader /> ) : error ? ( <Message variant="danger">{error}</Message> ) : ( <form onSubmit={handleSubmit}> <table className="table table-bordered"> <thead> <tr> <th scope="col">#</th> <th scope="col">Name</th> <th scope="col">Room no.</th> <th scope="col">First Time</th> <th scope="col">Second Time</th> </tr> </thead> <tbody> {users.map((user) => ( <tr key={user.id}> <th scope="row">{user.id}</th> <td className="form-group"> <input type="text" className="form-control" name="username" value={user.username} readOnly /> </td> <td className="form-group"> <input type="text" className="form-control" name="room" value={user.room} readOnly /> </td> <td> <select name={`firstAttendance${user.id}`} // 绑定对应的值,不存在就默认present value={attendanceMap[user.id]?.first_time || "present"} // 变更时调用处理函数,传入用户id和考勤类型 onChange={(e) => handleAttendanceChange(user.id, 'first_time', e.target.value)} className="form-control" > <option value="present">Present ✓</option> <option value="absent">Absent X</option> <option value="double">Double 2</option> </select> </td> <td> <select name={`secondAttendance${user.id}`} value={attendanceMap[user.id]?.second_time || "present"} onChange={(e) => handleAttendanceChange(user.id, 'second_time', e.target.value)} className="form-control" > <option value="present">Present ✓</option> <option value="absent">Absent X</option> <option value="double">Double 2</option> </select> </td> </tr> ))} </tbody> </table> <button type="submit" className="btn btn-primary"> Submit </button> </form> )} </section > ); }
补充说明
- 用户名和房间号的输入框加了
readOnly属性,因为这两个值是从用户列表直接带出来的,不需要用户编辑,也能避免React受控组件警告,同时移除了循环里重复的id属性,符合HTML规范。 - 这种以唯一ID为键存储列表项表单值的方案,是React处理循环渲染表单的通用写法,不管是多少行的表单,都不会出现值互相覆盖的问题,取值也直接读对应ID下的字段就行,不用费劲去DOM里捞元素的值。
- 如果后端支持批量提交,你也可以把所有考勤数据组装成数组一次性发过去,不用循环发单次请求,性能更好。
内容的提问来源于stack exchange,提问作者Hasnain Sikander
相关产品推荐
相关产品推荐

