Incorrect Logic to select a set of all checkboxes

Issue

Consider the following code, there are multiple checkboxes whose fields are:

<Checkbox
         checked={checked[element]}
         onChange={(e) => {
                            setChecked({ ...checked, [e.target.name]: !checked[e.target.name] });
                           }}
         name={element}
 />

Now consider a button used to check all the checkboxes:

<Button
       variant="contained"
       onClick={() => {
                        setIsAllChecked(!isAllChecked);
                        Object.keys(checked).forEach((e) => {
                                    setChecked({ ...checked, [e]: !checked[e] });
                                });
                      }}
       marginRight={2}
       sx={{ margin: '2rem' }}
   >
   {isAllChecked ? 'Unselect All' : 'Select All'}
</Button>

the onCLick logic renders through an array of all elements, and is supposed to set their value to true, but for some reason its only setting the value of the last checkbox as true. here is the checked state variable for your reference:

const [checked, setChecked] = useState({
        'Add Employee Importer': false,
        'Employee LOP': false,
        'LOP Reversal Importer': false,
        'Employee Bank Details': false,
        'Employee Loan Details': false,
        'Employee Category': false,
        'Employee Resignation': false,
        'Bulk Salary Information Of Employees': false,
        'Basic Employee Information': false,
        'Employee PF_ESI Details': false,
        'Final Settlement Details': false,
        'Add revised salary': false
    });

Solution

The issue is your loop here

Object.keys(checked).forEach((e) => {
   setChecked({ ...checked, [e]: !checked[e] });
});

setChecked is an asynchronous call. That is, your checked state wont change until the next render. When you set one state’s value to true, the next call will set it back to false, and its current key value to true. It will repeat that until you are at your last element. Hence, why only the last checkbox is true.

For example, consider this state

const [checked, setChecked] = useState({a : false, b: false})

Now consider this.

setChecked({ ...checked, a: true})
console.log(checked) // returns { a: false, b: false}
setChecked({ ...checked, b: true})

The second setChecked call overwrites the first call due to the ...checked, and since checked states has not changed due to the async nature, checked would only set key b to true

Instead for your code, you should make a copy of checked to do this.

const checkedCopy = { ...checked }
Object.keys(checkedCopy).forEach((e) => {
   checkedCopy.e = !checkedCopy.e;
});
setChecked(checkedCopy)

With this, you now only have 1 setChecked call.

Answered By – Bas bas

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published