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

React JS表格多开关Bug修复:解决单点击触发全选问题

问题修复方案

问题根源

你当前的代码中,所有同类型的开关(比如所有"Lihat"开关)都绑定了同一个全局状态变量和切换函数,导致点击任意一个开关时,全局状态同步改变,所有同类型开关跟着切换。

修复步骤

1. 重构状态结构

将分散的单个状态合并为一个对象,专门存储每个菜单(含子菜单)对应权限的独立状态。用useState管理这个对象,通过菜单ID、子菜单标识和权限类型组合成唯一键,确保每个开关对应独立的状态项。

2. 实现通用切换函数

创建一个通用切换函数,接收菜单ID、子菜单标识、权限类型作为参数,精准修改对应状态项,避免全局状态污染。

3. 更新开关绑定逻辑

渲染表格时,为每个开关绑定对应的独立状态值和切换函数,实现单个开关的独立控制。

完整修复代码

import React, { useEffect, useState } from "react";
import { Table, Form } from "react-bootstrap";

export default function RoleForm() {
  // 初始化权限状态:每个菜单/子菜单的权限默认未勾选
  const [permissionStates, setPermissionStates] = useState({});

  // 通用切换函数:精准定位对应权限状态并切换
  const togglePermission = (rowId, menuKey, permType) => {
    const stateKey = `${rowId}-${menuKey}-${permType}`;
    setPermissionStates(prev => ({
      ...prev,
      [stateKey]: !prev[stateKey]
    }));
  };

  useEffect(() => {
    console.log("权限状态:", permissionStates);
  }, [permissionStates]);

  const tableHeader = [
    "No",
    "Menu",
    "Sub Menu",
    "Lihat",
    "Tambah",
    "Ubah",
    "Hapus",
    "Export",
  ];

  const tableData = [
    {
      id: 1,
      menu: "Dashboard",
      submenu: ["-"],
      perms: ["lihat", null, null, null, "export"],
    },
    {
      id: 2,
      menu: "Konfigurasi/Pengaturan",
      submenu: ["Akun Pengguna", "Grup", "Antrian", "Rawat"],
      perms: ["lihat", "tambah", "edit", "hapus", null],
    },
  ];

  return (
    <Table className="table-responsive">
      <thead>
        <tr>
          {tableHeader.map((header, index) => (
            <th
              key={index}
              className="txt-blue-grey-700 base-md text-bold text-center"
              style={{ padding: "0.75rem" }}
            >
              {header}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {tableData.map((row, rowIndex) => (
          <React.Fragment key={row.id}>
            {/* 主菜单行 */}
            <tr>
              <td>{rowIndex + 1}</td>
              <td>{row.menu}</td>
              <td>{row.submenu[0]}</td>
              {/* Lihat开关 */}
              <td>
                <Form.Check
                  type="switch"
                  checked={permissionStates[`${row.id}-main-lihat`] || false}
                  onChange={() => togglePermission(row.id, "main", "lihat")}
                />
              </td>
              {/* Tambah开关 */}
              <td>
                <Form.Check
                  type="switch"
                  checked={permissionStates[`${row.id}-main-tambah`] || false}
                  onChange={() => togglePermission(row.id, "main", "tambah")}
                />
              </td>
              {/* Ubah开关 */}
              <td>
                <Form.Check
                  type="switch"
                  checked={permissionStates[`${row.id}-main-ubah`] || false}
                  onChange={() => togglePermission(row.id, "main", "ubah")}
                />
              </td>
              {/* Hapus开关 */}
              <td>
                <Form.Check
                  type="switch"
                  checked={permissionStates[`${row.id}-main-hapus`] || false}
                  onChange={() => togglePermission(row.id, "main", "hapus")}
                />
              </td>
              {/* Export开关 */}
              <td>
                <Form.Check
                  type="switch"
                  checked={permissionStates[`${row.id}-main-export`] || false}
                  onChange={() => togglePermission(row.id, "main", "export")}
                />
              </td>
            </tr>
            {/* 子菜单行 */}
            {row.submenu.slice(1).map((subMenu, subMenuIndex) => (
              <tr key={`${row.id}-sub-${subMenuIndex}`}>
                <td></td>
                <td></td>
                <td>{subMenu}</td>
                {/* 子菜单Lihat开关 */}
                <td>
                  <Form.Check
                    type="switch"
                    checked={permissionStates[`${row.id}-sub-${subMenuIndex}-lihat`] || false}
                    onChange={() => togglePermission(row.id, `sub-${subMenuIndex}`, "lihat")}
                  />
                </td>
                {/* 子菜单Tambah开关 */}
                <td>
                  <Form.Check
                    type="switch"
                    checked={permissionStates[`${row.id}-sub-${subMenuIndex}-tambah`] || false}
                    onChange={() => togglePermission(row.id, `sub-${subMenuIndex}`, "tambah")}
                  />
                </td>
                {/* 子菜单Ubah开关 */}
                <td>
                  <Form.Check
                    type="switch"
                    checked={permissionStates[`${row.id}-sub-${subMenuIndex}-ubah`] || false}
                    onChange={() => togglePermission(row.id, `sub-${subMenuIndex}`, "ubah")}
                  />
                </td>
                {/* 子菜单Hapus开关 */}
                <td>
                  <Form.Check
                    type="switch"
                    checked={permissionStates[`${row.id}-sub-${subMenuIndex}-hapus`] || false}
                    onChange={() => togglePermission(row.id, `sub-${subMenuIndex}`, "hapus")}
                  />
                </td>
                {/* 子菜单Export开关 */}
                <td>
                  <Form.Check
                    type="switch"
                    checked={permissionStates[`${row.id}-sub-${subMenuIndex}-export`] || false}
                    onChange={() => togglePermission(row.id, `sub-${subMenuIndex}`, "export")}
                  />
                </td>
              </tr>
            ))}
          </React.Fragment>
        ))}
      </tbody>
    </Table>
  );
}

关键说明

  • 通过permissionStates对象存储每个开关的独立状态,键采用${菜单ID}-${菜单类型(main/sub)}-${权限类型}的格式,确保唯一性。
  • togglePermission函数通过组合键精准定位要修改的状态项,避免全局状态影响其他开关。
  • 每个开关绑定对应的状态值(默认false)和切换函数,实现单个开关的独立控制,同时修正了原代码中表头与内容列不匹配的布局问题。

内容的提问来源于stack exchange,提问作者github.arief

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:24:59