I'm trying to create a list of nested/grouped checkboxes, where selecting one of the checkbox (parent) should select other checkboxes (all its children) based on a array of data objects. Right now, hard coding the checked
value in the App.js
state reflects the checkbox output ticks, but when the state is being modified by passing the setState
as props to the child components, the checkboxes do not update.
One hacky way of partially solving what I'm trying to do, is by using a useState
in the CheckBoxGroup.js
onChange methods, to change a boolean value, thus forcing the component to update. But even then, the console shows the correct changed props.state, but the app.js component state doesn't reflect. Without the useState, the checkboxes do not seem to re-render as well. Can someone help with this please?
Thanks
The following is the code. Code Sandbox link here: https://codesandbox.io/s/red-haze-ij1km?fontsize=14&hidenavigation=1&theme=dark
App.js
import React, { useState } from "react";
import "./styles.css";
import FoodGroup from "./FoodGroup";
export default function App() {
let arr = [
{
name: "Fruits",
parentId: 100,
data: [{ value: "Apple", id: 1 }, { value: "Banana", id: 2 }]
},
{
name: "Vegetables",
parentId: 200,
data: [{ value: "Potato", id: 3 }, { value: "Onion", id: 4 }]
}
];
const [state, setState] = useState({
// basically selecting all id : { selected : true } for parent id and child id
1: { selected: false },
2: { selected: false },
100: { selected: false }
});
return (
<div className="App">
<FoodGroup data={arr} state={state} setState={setState} />
</div>
);
}
FoodGroup.js
import React from "react";
import CheckBoxGroup from "./CheckBoxGroup";
const FoodGroup = props => {
let groups = props.data.map((group, index) => (
<CheckBoxGroup
group={group}
key={index}
data={props.data}
setState={props.setState}
state={props.state}
/>
));
return <ul>{groups}</ul>;
};
export default FoodGroup;
CheckBoxGroup.js
import React, { useState } from "react";
import CheckBox from "./CheckBox";
const CheckBoxGroup = props => {
// const [a, b] = useState(false);
const onChildChange = (child, e) => {
let childSelected = props.state;
if (!childSelected[child]) {
childSelected[child] = {};
}
childSelected[child].selected = e.target.checked;
props.setState(childSelected);
// b(!a); a hacky way I found a work around to force re render the component otherwise it doesn't
// work
};
const onParentChange = (child, e) => {
let childSelected = props.state;
if (!childSelected[child]) {
childSelected[child] = {};
}
let childInThatParent = [];
props.data.forEach(group => {
if (group.parentId === child) {
group.data.forEach(item => {
childInThatParent.push(item.id);
});
}
});
childSelected[child].selected = e.target.checked;
childInThatParent.forEach(child => {
if (!childSelected[child]) {
childSelected[child] = {};
}
childSelected[child].selected = e.target.checked;
});
props.setState(childSelected);
// b(!a);
};
let checkboxes = props.group.data.map((item, index) => (
<CheckBox
onChange={onChildChange}
value={item.value}
id={item.id}
key={item.id}
setState={props.setState}
state={props.state}
/>
));
return (
<React.Fragment>
<CheckBox
onChange={onParentChange}
value={props.group.name}
id={props.group.parentId}
countrySelected={props.state}
setState={props.setState}
state={props.state}
/>
<ul>{checkboxes}</ul>
</React.Fragment>
);
};
export default CheckBoxGroup;
CheckBox.js
import React, { useState } from "react";
const CheckBox = ({ state, id, onChange, value }) => {
return (
<li>
<input
type="checkbox"
value={id}
onChange={e => {
onChange(id, e);
}}
checked={state[id].selected}
/>
{value}
</li>
);
};
export default CheckBox;