How to enable a button when every checkbox in a table is checked

题意:如何在表格中的每个复选框被选中时启用一个按钮?

问题背景:

I need a button to be enabled when all the checkboxes in a table are checked and disabled otherwise.

我需要一个按钮,当表格中的所有复选框都被选中时启用,反之则禁用。

Outer component with button:   包含按钮的外部组件:

import Box from "@mui/system/Box";
import Stack from "@mui/system/Stack";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useState } from "react";
import HRMButton from "../Button/HRMButton";
import ToDoTable from "./ToDoTable";
import { fonts } from "../../Styles";

export default function OnboardingTasks({style}) {
    const tasks = [
        {
            name: 'Discuss and set your first 30/60/90-day goals with your manager',
            done: false
        },
        {
            name: 'Complete personal info & documents on Bluewave HRM',
            done: false
        },
        {
            name: 'Sign and submit essential documents',
            done: false
        }
    ];

    const [allTasksComplete, setAllTasksComplete] = useState(false);

    function checkTasks(tasks) {
        return tasks.every((task) => task.done);
    }

    return (
        <Box sx={{...{
            border: "1px solid #EBEBEB",
            borderRadius: "10px",
            minWidth: "1003px",
            paddingX: "113px",
            paddingY: "44px",
            fontFamily: fonts.fontFamily
        }, ...style}}>
            <h4 style={{textAlign: "center", marginTop: 0}}>Complete your to-do items</h4>
            <p style={{textAlign: "center", marginBottom: "50px"}}>
                You may discuss your to-dos with your manager
            </p>
            <ToDoTable 
                tasks={tasks} 
                onChange={() => setAllTasksComplete(checkTasks(tasks))} 
                style={{marginBottom: "50px"}}
            />
            <Stack direction="row" alignContent="center" justifyContent="space-between">
                <HRMButton mode="secondaryB" startIcon={<ArrowBackIcon />}>Previous</HRMButton>
                //This button needs to be enabled
                <HRMButton mode="primary" enabled={allTasksComplete}>Save and next</HRMButton>
            </Stack>
        </Box>
    );
};

Table component:    Table 组件

import TableContainer from "@mui/material/TableContainer";
import Table from "@mui/material/Table";
import TableHead from "@mui/material/TableHead";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import { styled } from "@mui/system";
import PropTypes from "prop-types";
import Checkbox from "../Checkbox/Checkbox";
import { fonts, colors } from "../../Styles";

export default function ToDoTable({tasks, onChange, style}) {
    //Custom style elements
    const TableHeaderCell = styled(TableCell)({
        color: colors.darkGrey,
        paddingTop: "10px",
        paddingBottom: "10px"
    });

    function handleChange(e, index, value) {
        console.log(tasks);
        tasks[index].done = value;
        onChange();
    }

    return (
        <TableContainer sx={{...{
            minWidth: "1003px",
            fontFamily: fonts.fontFamily
        }, ...style}}>
            <Table>
                <TableHead>
                    <TableRow sx={{backgroundColor: "#F9FAFB"}}>
                        <TableHeaderCell>
                            <b style={{color: colors.grey}}>To-Do</b>
                        </TableHeaderCell>
                        <TableHeaderCell align="right">
                            <b style={{color: colors.grey}}>Done</b>
                        </TableHeaderCell>
                    </TableRow>
                </TableHead>
                <TableBody>
                    {tasks.map((task, index) => (
                        <TableRow>
                            <TableCell>
                                {task.name}
                            </TableCell>
                            <TableCell align="right">
                                <Checkbox
                                    type="checkbox"
                                    id={`${task.name}-done`}
                                    name={`${task.name}-done`}
                                    value={`${task.name}-done`}
                                    size="large"
                                    onChange={(e) => handleChange(e, index, !task.done)}
                                />
                            </TableCell>
                        </TableRow>
                    ))}
                </TableBody>
            </Table>
        </TableContainer>
    );
};

When using React useState like in the code example given, the entire OnboardingTasks component rerenders every time the AllTasksComplete state is changed which resets the status of all the tasks back to false. When I use React useRef the button is not rerendered and doesn't react to the change in AllTasksComplete.

当像代码示例中那样使用 React 的 `useState` 时,每次 `AllTasksComplete` 状态改变时,整个 `OnboardingTasks` 组件都会重新渲染,这会将所有任务的状态重置为 `false`。而当我使用 React 的 `useRef` 时,按钮不会重新渲染,因此不会对 `AllTasksComplete` 的变化做出反应。

How do I enable the button when all the checkboxes are checked while retaining the status of the tasks variable?

如何在保留 `tasks` 变量状态的同时,启用按钮当所有复选框都被选中时?

Buttons and checkboxes in this example are custom variations from MUI. If you need to run this code in your local machine, leave a comment and I'll provide the code.

按钮和复选框在此示例中是 MUI 的自定义变体。如果您需要在本地机器上运行此代码,请留言,我会提供代码。

问题解决:

Most of your code is well set, we need to discuss only the below exception.

你的大部分代码设置得很好,我们只需要讨论以下例外情况。

The exception:        异常:

Although the intent of the below code is correct, the implementation is incorrect. The code is intended to keep the tasks updated with respect to the user interaction. It is absolutely required. However, implementing the same by changing props is incorrect. It affects the purity of the component. Impure components are inconsistent in rendering. You can read more about it from here : Keeping Components Pure.

尽管下面代码的意图是正确的,但实现是错误的。该代码旨在根据用户交互保持任务的更新。这是绝对必要的。然而,通过更改 props 来实现相同的目的是不正确的。这会影响组件的纯度。不纯的组件在渲染时是不一致的。你可以从这里阅读更多关于保持组件纯的内容:保持组件的纯净性。

tasks[index].done = value; // handleChange in ToDoTable

Therefore please try refactoring the code to meet intent implemented in the most recommended way. One of the refactored versions may be as below.

因此,请尝试重构代码,以实现最推荐的意图。重构后的版本之一可能如下所示。

A solution:        一个解决方案

// a) Bring the tasks down into the component ToDoTable

// b) And make it a state over there

Please see below a sample code in the same line in its most basic form.

请参见下面一行的示例代码,以其最基本的形式展示。

App.js

import { useState } from 'react';

export default function OnboardingTasks() {
  const [allTasksComplete, setAllTasksComplete] = useState(false);
  return (
    <>
      <ToDoTable onCompletingTask={setAllTasksComplete} />
      <button
        disabled={!allTasksComplete}
        onClick={() => console.log('a handler to follow')}
      >
        Save and Next
      </button>
    </>
  );
}

function ToDoTable({ onCompletingTask }) {

  // please note a new property sortorder added
  // and is used to keep the order in rendering
  const initialTasklist = [
    {
      name: 'Discuss and set your first 30/60/90-day goals with your manager',
      done: false,
      sortorder: 1,
    },
    {
      name: 'Complete personal info & documents on Bluewave HRM',
      done: false,
      sortorder: 2,
    },
    {
      name: 'Sign and submit essential documents',
      done: false,
      sortorder: 3,
    },
  ];
  const [tasks, setTasks] = useState(initialTasklist);

  function handleClick(updatedTask) {
    // updating tasks state without mutating
    // step 1: filter out the tasks except the currently updated one
    const tasktemp = tasks.filter(
      (tasktemp) => tasktemp.sortorder !== updatedTask.sortorder
    );
    // step 2: Add the currently updated task into the list of other tasks
    tasktemp.push(updatedTask);
    // update the tasks state
    setTasks(tasktemp);
    // update the task completion status in the parent object
    onCompletingTask(tasktemp.filter((task) => !task.done).length == 0);
  }
  return (
    <>
      Tasks:
      <ul>
        {tasks
          .sort((task1, task2) => task1.sortorder - task2.sortorder)
          .map((task) => (
            <li key={task.sortorder}>
              <label>
                {task.name}
                <input
                  type="checkbox"
                  value={task.done}
                  onChange={(e) =>
                    handleClick({
                      name: task.name,
                      done: e.target.checked,
                      sortorder: task.sortorder,
                    })
                  }
                />
              </label>
            </li>
          ))}
      </ul>
    </>
  );
}

Test run:

On load of the app, the button is disabled

应用加载时,按钮处于禁用状态。

Browser display on load of the app

On completing the first task, still the button is disabled

在完成第一个任务后,按钮仍然处于禁用状态。

Browser display - On completing the first task

On completing the last task, the button is enabled

在完成最后一个任务后,按钮被启用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

营赢盈英

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值