I'm new to React, I'm trying to write a component that hold different groups, each group have accumulated amount of points and each group hold different checkboxes, I want that when the checkbox is clicked the amount will update in the correct way (add/subtract depend on the checkbox state)
Here is my code where I tried to check if its working(I've omitted unrelated code and includes):
export default function Tracker() {
const [amountMust, setAmountMust] = React.useState(0); // 84
const [amountListA, setAmountListA] = React.useState(0); // 24.5
const [amountMalag, setAmountMalag] = React.useState(0); //6
const [amountSport, setAmountSport] = React.useState(0); //2
const [amountScience, setAmountScience] = React.useState(0); // 8
const [amountGeneral, setAmountGeneral] = React.useState(0); // 2
const [amountProject, setAmountProject] = React.useState(0); // 2
const [amountEnglish, setAmountEnglish] = React.useState(0); // 2
const courses = [
{ number: "101", name: "Mathematics", points: 3 },
{ number: "102", name: "Physics", points: 4 },
{ number: "103", name: "Chemistry", points: 2 },
];
const CheckBoxState = ({ points, groupSetter }) => {
const [isChecked, setIsChecked] = React.useState(false);
const handleCheckboxChange = (e) => {
const newChecked = e.target.checked;
setIsChecked(newChecked);
groupSetter ((prevGroupPoints) =>
newChecked ? prevGroupPoints + points : prevGroupPoints - points);
};
return (
<Checkbox
variant="soft"
size="md"
color="success"
checked={isChecked}
onChange={handleCheckboxChange}
/>
);
};
const CourseList = ({ courses }) => {
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{courses.map((course) => (
<FormControl
sx={{
display: "flex",
flexDirection: "row",
gap: 1,
border: "1px solid #ccc",
borderRadius: 2,
padding: 2,
}}
>
<Box sx={{ display: "fixed", alignItems: "center", gap: 2 }}>
<CheckBoxState points={course.points} groupSetter={setAmountMust}/>
<Typography variant="body2">{course.number}</Typography>
<Typography variant="body2">{course.name}</Typography>
<Typography variant="body2">נק"ז: {course.points}</Typography>
</Box>
<Box sx={{ display: "flex", gap: 2, mt: 2, marginLeft: 1 }}>
<FormControl>
<FormLabel>Option 1</FormLabel>
<Switch size="sm" />
</FormControl>
<FormControl>
<FormLabel>Option 2</FormLabel>
<Switch size="sm" />
</FormControl>
</Box>
</FormControl>
))}
</Box>
);
};
return (
<div className='box'>
<CourseList />
</div>
);
}
the problem is when calling groupSetter
inside handleCheckboxChange
the checkbox state does not change and keeps unchecked, and the value is added to amountMust
over and over again at each click, but when removing groupSetter
or passing it a regular value that does not depend on the previous state it works.
The purpose of passing the state setter and value to the checkbox component is because I want to use this component for different groups in the code.
Don't declare React components inside other React components. The problem here is that each time the state updates in Tracker
, both CheckBoxState
and CourseList
are re-declared and are new React component references. React will unmount the previous "versions/instances" and mount the new "versions/instances". Any state and UI they had will be reset as well.
Move these component declarations outside the Tracker
component declaration and pass in the props they need instead of relying on the Javascript closure the Tracker
function provides.
Example:
const courses = [
{ number: "101", name: "Mathematics", points: 3 },
{ number: "102", name: "Physics", points: 4 },
{ number: "103", name: "Chemistry", points: 2 },
];
const CheckBoxState = ({ points, setAmountMust }) => {
const [isChecked, setIsChecked] = React.useState(false);
const handleCheckboxChange = (e) => {
const { checked } = e.target;
setIsChecked(checked);
setAmountMust((amountMust) => checked
? amountMust + points
: amountMust - points
);
};
return (
<Checkbox
variant="soft"
size="md"
color="success"
checked={isChecked}
onChange={handleCheckboxChange}
/>
);
};
const CourseList = ({ courses, setAmountMust }) => {
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{courses.map((course) => (
<FormControl
key={course.name}
sx={{
display: "flex",
flexDirection: "row",
gap: 1,
border: "1px solid #ccc",
borderRadius: 2,
padding: 2,
}}
>
<Box sx={{ display: "fixed", alignItems: "center", gap: 2 }}>
<CheckBoxState
points={course.points}
setAmountMust={setAmountMust}
/>
<Typography variant="body2">{course.number}</Typography>
<Typography variant="body2">{course.name}</Typography>
<Typography variant="body2">נק"ז: {course.points}</Typography>
</Box>
<Box sx={{ display: "flex", gap: 2, mt: 2, marginLeft: 1 }}>
<FormControl>
<FormLabel>Option 1</FormLabel>
<Switch size="sm" />
</FormControl>
<FormControl>
<FormLabel>Option 2</FormLabel>
<Switch size="sm" />
</FormControl>
</Box>
</FormControl>
))}
</Box>
);
};
export default function Tracker() {
const [amountMust, setAmountMust] = React.useState(0); // 84
const [amountListA, setAmountListA] = React.useState(0); // 24.5
const [amountMalag, setAmountMalag] = React.useState(0); //6
const [amountSport, setAmountSport] = React.useState(0); //2
const [amountScience, setAmountScience] = React.useState(0); // 8
const [amountGeneral, setAmountGeneral] = React.useState(0); // 2
const [amountProject, setAmountProject] = React.useState(0); // 2
const [amountEnglish, setAmountEnglish] = React.useState(0); // 2
return (
<div className='box'>
<CourseList
courses={courses}
setAmountMust={setAmountMust}
/>
</div>
);
}