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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 19:09:22