【问题标题】:Selection Checkbox in React using Hooks使用 Hooks 在 React 中选择复选框
【发布时间】:2021-10-06 15:33:34
【问题描述】:

我在 React 的表格中选择单个复选框或多个复选框时遇到问题。我正在使用 Material-UI。请在此处查看我的代码框

CLICK HERE

我想在下图中实现这样的效果:

  <TableContainer className={classes.tableContainer}>
    <Table>
      <TableHead className={classes.tableHead}>
        <TableRow>
          <TableCell padding="checkbox">
            <Checkbox
              checked={false}
              inputProps={{ "aria-label": "select all desserts" }}
            />
          </TableCell>
          {head.map((el) => (
            <TableCell key={el} align="left">
              {el}
            </TableCell>
          ))}
        </TableRow>
      </TableHead>
      <TableBody>
        {body?.excluded_persons?.map((row, index) => (
          <TableRow key={row.id}>
            <TableCell padding="checkbox">
              <Checkbox checked={true} />
            </TableCell>
            <TableCell align="left">{row.id}</TableCell>
            <TableCell align="left">{row.name}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  </TableContainer>

【问题讨论】:

  • 在我看来,您甚至没有尝试使用状态管理。复选框可能被卡住了,因为您正在硬编码它们的选中值,如下所示:
  • @Inbar Koursh。对不起,我刚刚删除了它,因为我不确定我的工作是否正确,以免混淆人们

标签: javascript reactjs ecmascript-6 react-hooks material-ui


【解决方案1】:

似乎您只是缺少本地组件状态来跟踪每个复选框的选中状态,包括表头中的复选框。

这里是AddedPersons 组件的实现,因为它有不止一行数据,所以更有趣。

  1. 创建状态以保存所选人员的状态。只添加额外的本地状态,不需要复制传递的body prop 数据(无论如何这都是反模式),也不需要添加任何派生状态,即不确定或全选(也是反模式)。

    const [allSelected, setAllSelected] = React.useState(false);
    const [selected, setSelected] = React.useState({});
    
  2. 创建处理程序以切换状态。

    const toggleAllSelected = () => setAllSelected((t) => !t);
    
    const toggleSelected = (id) => () => {
      setSelected((selected) => ({
        ...selected,
        [id]: !selected[id]
      }));
    };
    
  3. allSelected 状态更新时,使用useEffect 挂钩切换所有选定的用户。

    React.useEffect(() => {
      body.persons?.added_persons &&
        setSelected(
          body.persons.added_persons.reduce(
            (selected, { id }) => ({
              ...selected,
              [id]: allSelected
            }),
            {}
          )
        );
    }, [allSelected, body]);
    
  4. 计算所选人数以确定是手动选择所有用户还是“不确定”。

    const selectedCount = Object.values(selected).filter(Boolean).length;
    
    const isAllSelected = selectedCount === body?.persons?.added_persons?.length;
    
    const isIndeterminate =
      selectedCount && selectedCount !== body?.persons?.added_persons?.length;
    
  5. 附加所有状态和回调处理程序。

    return (
      <>
        <TableContainer className={classes.tableContainer}>
          <Table>
            <TableHead className={classes.tableHead}>
              <TableRow>
                <TableCell colSpan={4}>{selectedCount} selected</TableCell>
              </TableRow>
              <TableRow>
                <TableCell padding="checkbox">
                  <Checkbox
                    checked={allSelected || isAllSelected}      // <-- all selected
                    onChange={toggleAllSelected}                // <-- toggle state
                    indeterminate={isIndeterminate}             // <-- some selected
                    inputProps={{ "aria-label": "select all desserts" }}
                  />
                </TableCell>
                ...
              </TableRow>
            </TableHead>
            <TableBody>
              {body?.persons?.added_persons?.map((row, index) => (
                <TableRow key={row.id}>
                  <TableCell padding="checkbox">
                    <Checkbox
                      checked={selected[row.id] || allSelected} // <-- is selected
                      onChange={toggleSelected(row.id)}         // <-- toggle state
                    />
                  </TableCell>
                  <TableCell align="left">{row.id}</TableCell>
                  <TableCell align="left">{row.name}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
      </>
    );
    

更新

似乎在我的第一个实现中存在一个错误,即在选中全选复选框时不允许手动取消选择人员。解决方法是将useEffect 中的逻辑移动到toggleAllSelected 处理程序中,并使用onChange 事件来切换所有正确的状态。还可以在toggleSelected 中添加一个检查,以在取消选择任何人的复选框时取消选择“全选”。

const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});

const toggleAllSelected = (e) => {
  const { checked } = e.target;
  setAllSelected(checked);

  body?.persons?.added_persons &&
    setSelected(
      body.persons.added_persons.reduce(
        (selected, { id }) => ({
          ...selected,
          [id]: checked
        }),
        {}
      )
    );
};

const toggleSelected = (id) => (e) => {
  if (!e.target.checked) {
    setAllSelected(false);
  }

  setSelected((selected) => ({
    ...selected,
    [id]: !selected[id]
  }));
};

注意: 由于AddedPersonsExcludedPersons 组件基本上是相同的组件,即它是一个具有相同标题、行渲染和选定状态的表格,您应该将它们重构为单个表组件,只需传入不同的行数据。这将使您的代码更加DRY

【讨论】:

  • @Joseph 已修复。 isIndeterminate 现在被强制转换为布尔类型,并且您有一个额外的标题列,所以我删除了它,现在有三个表列。啊,是的,我现在看到了错误问题,正在调查。
  • 第一张桌子上似乎还有一个错误。单击检查所有复选框的主复选框。然后单击一个复选框,它不会取消选中
  • @Joseph 解决了取消选择的问题。我在回答中包含的注释涵盖了让第二张桌子(被排除在外的人)工作。由于这两个组件基本相同,因此重构上述代码以获取行数据数组并在两个地方使用比将逻辑复制到两个组件更有意义。如果您需要这方面的帮助,或者我所描述的内容没有意义,请告诉我。
  • @Joseph 这是一个forked codesandbox,其中包含一个表格组件,其中不同的数据作为道具传递给它。行数据只是数据,因此将行数据从一个移动到另一个应该是相当简单的。
  • 你好,德鲁。你知道吗?顺便说一下,这是在 NextJS 中。 stackoverflow.com/questions/68450181/…
【解决方案2】:

我已将您添加的人员表更新如下,

请注意,我是使用组件状态来更新表格状态的,

const AddedPersons = ({ classes, head, body }) => {

  const [addedPersons, setAddedPersons] = useState(
    body?.persons?.added_persons.map((person) => ({
      ...person,
      checked: false
    }))
  );

  const [isAllSelected, setAllSelected] = useState(false);
  const [isIndeterminate, setIndeterminate] = useState(false);

  const onSelectAll = (event) => {
    setAllSelected(event.target.checked);
    setIndeterminate(false);
    setAddedPersons(
      addedPersons.map((person) => ({
        ...person,
        checked: event.target.checked
      }))
    );
  };

  const onSelect = (event) => {
    const index = addedPersons.findIndex(
      (person) => person.id === event.target.name
    );
    // shallow clone
    const updatedArray = [...addedPersons];
    updatedArray[index].checked = event.target.checked;
    setAddedPersons(updatedArray);

    // change all select checkbox
    if (updatedArray.every((person) => person.checked)) {
      setAllSelected(true);
      setIndeterminate(false);
    } else if (updatedArray.every((person) => !person.checked)) {
      setAllSelected(false);
      setIndeterminate(false);
    } else {
      setIndeterminate(true);
    }
  };

  const numSelected = addedPersons.reduce((acc, curr) => {
    if (curr.checked) return acc + 1;
    return acc;
  }, 0);

  return (
    <>
      <Toolbar>
        {numSelected > 0 ? (
          <Typography color="inherit" variant="subtitle1" component="div">
            {numSelected} selected
          </Typography>
        ) : (
          <Typography variant="h6" id="tableTitle" component="div">
            Added Persons
          </Typography>
        )}
      </Toolbar>
      <TableContainer className={classes.tableContainer}>
        <Table>
          <TableHead className={classes.tableHead}>
            <TableRow>
              <TableCell padding="checkbox">
                <Checkbox
                  checked={isAllSelected}
                  inputProps={{ "aria-label": "select all desserts" }}
                  onChange={onSelectAll}
                  indeterminate={isIndeterminate}
                />
              </TableCell>
              {head.map((el) => (
                <TableCell key={el} align="left">
                  {el}
                </TableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>
            {addedPersons?.map((row, index) => (
              <TableRow key={row.id}>
                <TableCell padding="checkbox">
                  <Checkbox
                    checked={row.checked}
                    onChange={onSelect}
                    name={row.id}
                  />
                </TableCell>
                <TableCell align="left">{row.id}</TableCell>
                <TableCell align="left">{row.name}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
    </>
  );
};

export default AddedPersons;

请参考这里的工作示例:https://codesandbox.io/s/redux-react-forked-cuy51

【讨论】:

  • 谢谢。我认为第二个表的复选框在您的代码框中不起作用
  • 是的。那是因为我没有为第二个表添加逻辑。您也可以对第二个表重复使用相同的逻辑。
  • 你也忘了添加这些。 ibb.co/zhyX3pN.
  • 请检查我更新的答案。但是,当我浏览文档时,我发现了这些示例:material-ui.com/components/tables 请也浏览这些。
猜你喜欢
  • 2021-02-03
  • 1970-01-01
  • 2020-01-26
  • 2021-08-18
  • 1970-01-01
  • 2021-08-08
  • 1970-01-01
  • 2019-05-20
  • 1970-01-01
相关资源
最近更新 更多