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:
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
.
How do I enable the button when all the checkboxes are checked while retaining the status of the tasks
variable?
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.
2
Answers
You are missing some React basics. The main issue is that the
tasks
data you are using is declared insideOnboardingTasks
, so any timeOnboardingTasks
renders for any reason,tasks
is redeclared with thedone: false
properties.tasks
should be the React state, and theallTasksComplete
should not be in state since it is a derived value from thetasks
"source of truth" state.Suggested refactor:
tasks
to local stateallTasksComplete
to a memoized derived valueOnboardingTasks
and pass down toToDoTable
as propsTableHeaderCell
declaration outside ReactTree so it’s a stable reference and not redeclared each render cycleMost 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.
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:
Please see below a sample code in the same line in its most basic form.
App.js
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