I’m doing this exercise: https://www.reacterry.com/portal/challenges/select-all-checkboxes
If you look under the checkOnChange function I tried doing it in multiple different ways but they don’t seem to work and I want to really understand why.
I’m just confused about the setCheckboxes part.
Theirs:
setCheckboxes(
checkboxes.map((checkbox) =>
checkbox.id === id
? { ...checkbox, checked: !checkbox.checked }
: checkbox
)
);
Mine:
//WHY DOESNT THIS WORK?
const arr = checkboxes;
const index = arr.findIndex((x) => x.id === id);
console.log(index);
console.log(arr);
arr[index].checked = !arr[index].checked;
setCheckboxes(arr);
console.log(checkboxes);
//WHY DOESNT THIS WORK?
setCheckboxes((prevState) => {
const arr = prevState;
const index = arr.findIndex((x) => x.id === id);
console.log(index);
console.log(arr);
arr[index].checked = !arr[index].checked;
return arr;
});
//WHY DOESNT THIS WORK?
setCheckboxes(
checkboxes.forEach((checkbox) => {
if (checkbox.id === id) {
checkbox = { ...checkbox, checked: !checkbox.checked }
}
})
);`
THE SOLUTION that I don’t fully understand the difference between my way and this way:
import React, { useState } from 'react';
import styled from 'styled-components';
const CheckboxList = () => {
const [checkboxes, setCheckboxes] = useState([
{ id: 1, label: 'Dogs', checked: false },
{ id: 2, label: 'Cats', checked: false },
{ id: 3, label: 'Cows', checked: false },
{ id: 4, label: 'Deers', checked: false },
]);
const handleCheckboxChange = (id) => {
setCheckboxes(
checkboxes.map((checkbox) =>
checkbox.id === id
? { ...checkbox, checked: !checkbox.checked }
: checkbox
)
);
};
const handleSelectAll = () => {
setCheckboxes(checkboxes.map((checkbox) => ({ ...checkbox, checked: true })));
};
return (
<Container>
<CheckboxContainer data-testid="checkbox-container">
{checkboxes.map((checkbox, index) => (
<CheckboxLabel key={checkbox.id}>
<input
data-testid={`checkbox-${index + 1}`}
type="checkbox"
checked={checkbox.checked}
onChange={() => handleCheckboxChange(checkbox.id)}
/>
{checkbox.label}
</CheckboxLabel>
))}
</CheckboxContainer>
<SelectAllButton data-testid="button" onClick={handleSelectAll}>Select All</SelectAllButton>
</Container>
);
};
export default CheckboxList;
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
margin: 24px;
`;
const CheckboxContainer = styled.div`
display: flex;
flex-direction: column;
gap: 5px;
`;
const CheckboxLabel = styled.label`
display: flex;
align-items: center;
gap: 5px;
`;
const SelectAllButton = styled.button`
padding: 10px 20px;
font-size: 18px;
border: none;
border-radius: 4px;
background-color: #333;
color: #fff;
cursor: pointer;
margin-top: 24px;
&:hover {
opacity: 0.8;
}
`;
MY CODE IS BELOW:
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
const CheckboxList = () => {
const [checkboxes, setCheckboxes] = useState([
{ id: 1, label: 'Dogs', checked: false },
{ id: 2, label: 'Cats', checked: false },
{ id: 3, label: 'Cows', checked: false },
{ id: 4, label: 'Deers', checked: false },
]);
const checkedOnChange = (id) => {
//WHY DOESNT THIS WORK?
const arr = checkboxes;
const index = arr.findIndex((x) => x.id === id);
console.log(index);
console.log(arr);
arr[index].checked = !arr[index].checked;
setCheckboxes(arr);
console.log(checkboxes);
//WHY DOESNT THIS WORK?
setCheckboxes((prevState) => {
const arr = prevState;
const index = arr.findIndex((x) => x.id === id);
console.log(index);
console.log(arr);
arr[index].checked = !arr[index].checked;
return arr;
});
//WHY DOESNT THIS WORK?
setCheckboxes(
checkboxes.forEach((checkbox) => {
if (checkbox.id === id) {
checkbox = { ...checkbox, checked: !checkbox.checked }
}
})
);
};
return (
<Container>
<CheckboxContainer data-testid="checkbox-container">
{checkboxes.map((checkbox, index) => (
<CheckboxLabel key={checkbox.id}>
<input
data-testid={`checkbox-${index + 1}`}
type="checkbox"
checked={checkbox.checked}
onClick={() => {
checkedOnChange(checkbox.id);
}}
/>
{checkbox.label}
</CheckboxLabel>
))}
</CheckboxContainer>
<SelectAllButton data-testid="button">Select All</SelectAllButton>
</Container>
);
};
export default CheckboxList;
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
margin: 24px;
`;
const CheckboxContainer = styled.div`
display: flex;
flex-direction: column;
gap: 5px;
`;
const CheckboxLabel = styled.label`
display: flex;
align-items: center;
gap: 5px;
`;
const SelectAllButton = styled.button`
padding: 10px 20px;
font-size: 18px;
border: none;
border-radius: 4px;
background-color: #333;
color: #fff;
cursor: pointer;
margin-top: 24px;
&:hover {
opacity: 0.8;
}
`;
I tried 3 different ways to do it and I’m not sure why it doesn’t work. I’d like to fully understand why. And see a solution that is written in the simplest code possible so I can reference it. When I say simple I mean preferably without fancy syntax or the spread operator. Just to get an idea. Thanks guys.
2
Answers
In your version:
The
arr
being returned is the same array instance asprevState
. The data in that array has been mutated (which one should not do in React state), and the array itself is the same reference (which means React doesn’t see that state has changed).The same is essentially true in all of your versions. You’re drilling down into the array and modifying its contents, and returning the same array.
Contrast this with their version:
The first thing to notice here is that the return value of
.map()
is set to the new state, and.map()
creates a new array. So React will observe that the array reference is different and state has been updated.The second thing to notice is that this array is made up of two things:
So overall the takeaway here is that when updating state you should always create new instances of any object or array which needs to be modified, never modify the current one(s).
You are trying to modify the same object in memory via running
The react way is functional: you pass an object to a function => you get the result. No outer modifications of the argument in between of the function call are expected, which can lead to odd bugs.
A good rule of thumb: if you need to pass a modified version of an object to
setHook
, create a new one, so it does not affect the old data flows/execution.Array.map()
creates a new array object, so thecheckboxes.map(...)
is used.{ ...checkbox, checked: !checkbox.checked }
also creates a new object.React subscribes to object changes in
setState
. Array in your case did not change (the same memory address), so React thinks that there was not state changes.