Selection Checkbox In React Using Hooks
Solution 1:
Seems you are just missing local component state to track the checked status of each checkbox, including the checkbox in the table header.
Here is the implementation for the AddedPersons
component since it's more interesting because it has more than one row of data.
Create state to hold the selected persons state. Only add the additional local state, no need to duplicate the passed
body
prop data (this is anti-pattern anyway) nor add any derived state, i.e. is indeterminate or is all selected (also anti-pattern).const [allSelected, setAllSelected] = React.useState(false); const [selected, setSelected] = React.useState({});
Create handlers to toggle the states.
consttoggleAllSelected = () => setAllSelected((t) => !t); consttoggleSelected = (id) => () => { setSelected((selected) => ({ ...selected, [id]: !selected[id] })); };
Use a
useEffect
hook to toggle all the selected users when theallSelected
state is updated.React.useEffect(() => { body.persons?.added_persons && setSelected( body.persons.added_persons.reduce( (selected, { id }) => ({ ...selected, [id]: allSelected }), {} ) ); }, [allSelected, body]);
Compute the selected person count to determine if all users are selected manually or if it is "indeterminate".
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;
Attach all the state and callback handlers.
return ( <><TableContainerclassName={classes.tableContainer}><Table><TableHeadclassName={classes.tableHead}><TableRow><TableCellcolSpan={4}>{selectedCount} selected</TableCell></TableRow><TableRow><TableCellpadding="checkbox"><Checkboxchecked={allSelected || isAllSelected} // <--allselectedonChange={toggleAllSelected} // <--togglestateindeterminate={isIndeterminate} // <--someselectedinputProps={{ "aria-label": "selectalldesserts" }} /></TableCell> ... </TableRow></TableHead><TableBody> {body?.persons?.added_persons?.map((row, index) => ( <TableRowkey={row.id}><TableCellpadding="checkbox"><Checkboxchecked={selected[row.id] || allSelected} // <--isselectedonChange={toggleSelected(row.id)} // <--togglestate /></TableCell><TableCellalign="left">{row.id}</TableCell><TableCellalign="left">{row.name}</TableCell></TableRow> ))} </TableBody></Table></TableContainer></> );
Update
Seems there was a bug in my first implementation that disallowed manually deselecting people while the select all checkbox was checked. The fix is to move the logic in the useEffect
into the toggleAllSelected
handler and use the onChange
event to toggle all the correct states. Also to add a check to toggleSelected
to deselect "select all" when any person checkboxes have been deselected.
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
consttoggleAllSelected = (e) => {
const { checked } = e.target;
setAllSelected(checked);
body?.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: checked
}),
{}
)
);
};
consttoggleSelected = (id) => (e) => {
if (!e.target.checked) {
setAllSelected(false);
}
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Note: Since both AddedPersons
and ExcludedPersons
components are basically the same component, i.e. it's a table with same headers and row rendering and selected state, you should refactor these into a single table component and just pass in the row data that is different. This would make your code more DRY.
Solution 2:
I have updated your added person table as below,
please note that I am using the component state to update the table state,
constAddedPersons = ({ 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);
constonSelectAll = (event) => {
setAllSelected(event.target.checked);
setIndeterminate(false);
setAddedPersons(
addedPersons.map((person) => ({
...person,
checked: event.target.checked
}))
);
};
constonSelect = (event) => {
const index = addedPersons.findIndex(
(person) => person.id === event.target.name
);
// shallow cloneconst updatedArray = [...addedPersons];
updatedArray[index].checked = event.target.checked;
setAddedPersons(updatedArray);
// change all select checkboxif (updatedArray.every((person) => person.checked)) {
setAllSelected(true);
setIndeterminate(false);
} elseif (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 ? (
<Typographycolor="inherit"variant="subtitle1"component="div">
{numSelected} selected
</Typography>
) : (
<Typographyvariant="h6"id="tableTitle"component="div">
Added Persons
</Typography>
)}
</Toolbar><TableContainerclassName={classes.tableContainer}><Table><TableHeadclassName={classes.tableHead}><TableRow><TableCellpadding="checkbox"><Checkboxchecked={isAllSelected}inputProps={{ "aria-label": "selectalldesserts" }}
onChange={onSelectAll}indeterminate={isIndeterminate}
/></TableCell>
{head.map((el) => (
<TableCellkey={el}align="left">
{el}
</TableCell>
))}
</TableRow></TableHead><TableBody>
{addedPersons?.map((row, index) => (
<TableRowkey={row.id}><TableCellpadding="checkbox"><Checkboxchecked={row.checked}onChange={onSelect}name={row.id}
/></TableCell><TableCellalign="left">{row.id}</TableCell><TableCellalign="left">{row.name}</TableCell></TableRow>
))}
</TableBody></Table></TableContainer></>
);
};
exportdefaultAddedPersons;
Please refer to this for a working example: https://codesandbox.io/s/redux-react-forked-cuy51
Post a Comment for "Selection Checkbox In React Using Hooks"