题意:如何在表格中的每个复选框被选中时启用一个按钮?
问题背景:
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
应用加载时,按钮处于禁用状态。
On completing the first task, still the button is disabled
在完成第一个任务后,按钮仍然处于禁用状态。
On completing the last task, the button is enabled
在完成最后一个任务后,按钮被启用。