Reputation: 98
I've got a list of items, each with their own checkboxes and I've decided to try add a 'select all' checkbox to make it easier for the user to select them all at once.
Unfortunately I'm finding it hard to work out the logic in a 'React' kinda way.
I found a JSBIN of how I would like the rendered result to work - https://jsbin.com/jetalaxaha/edit?html,js,output note: this is setup in a different way to how I would like it to.
My current code is here:
import React, { Component } from "react";
import ReactDOM from "react-dom";
class Items extends Component {
state = {
categories: [
{
id: 1,
name: "category 1",
items: [
{ name: "item 1", id: Math.floor(Math.random() * 99999) },
{ name: "item 2", id: Math.floor(Math.random() * 99999) }
]
},
{
id: 2,
name: "category 2",
items: [
{ name: "item 3", id: Math.floor(Math.random() * 99999) },
{ name: "item 4", id: Math.floor(Math.random() * 99999) }
]
},
{
id: 3,
name: "category 3",
items: [
{ name: "item 5", id: Math.floor(Math.random() * 99999) }
]
}
],
checkedListAll: [],
ItemsChecked: false
};
selectedItems(e) {
const { value, checked } = e.target;
let { checkedListAll } = this.state;
if (checked) {
checkedListAll = [...checkedListAll, value];
} else {
checkedListAll = checkedListAll.filter(el => el !== value);
if (this.state.ItemsChecked) {
this.setState({
ItemsChecked: !this.state.ItemsChecked
});
}
}
this.setState({ checkedListAll });
}
selectItem(e) {
const { checked } = e.target;
const { categories } = this.state;
const collection = [];
if (checked) {
this.setState(
{
checkedListAll: []
},
() => {
for (const cat of categories) {
for (const item of cat.items) {
collection.push(item.id);
}
}
this.setState({
checkedListAll: collection
});
}
);
} else {
this.setState({
checkedListAll: []
});
}
this.setState({
ItemsChecked: !this.state.ItemsChecked
});
}
render() {
const { categories, checkedListAll, ItemsChecked } = this.state;
return (
<div>
<header>
<label>
<input
type="checkbox"
checked={ItemsChecked}
onClick={this.selectItem.bind(this)}
/>Select all
</label>
</header>
{categories.map(cat => {
return (
<ItemCategory
{...cat}
key={cat.id}
click={this.openModal}
selectedItems={this.selectedItems.bind(this)}
ItemsChecked={ItemsChecked}
/>
);
})}
{
<pre>
All Selected: {JSON.stringify(ItemsChecked, null, 2)}
</pre>
}
{
<pre>
Selected List: {JSON.stringify(checkedListAll, null, 2)}
</pre>
}
</div>
);
}
}
class ItemCategory extends Component {
render() {
const { items, name, selectedItems, ItemsChecked } = this.props;
const getItems = items.map(item => {
return item;
});
return (
<div>
<div>-{name}</div>
<ul>
{getItems.map(item => {
return (
<li key={item.id}>
<Checkbox
item={item}
selectedItems={selectedItems}
ItemsChecked={ItemsChecked}
/>
</li>
);
})}
</ul>
</div>
);
}
}
class Checkbox extends Component {
state = {
isChecked: false
};
componentDidUpdate(prevProps) {
if (prevProps.ItemsChecked !== this.props.ItemsChecked) {
this.setState({
isChecked: !this.state.isChecked
});
}
}
handleClick(e) {
e.persist();
if (this.props.ItemsChecked) {
console.log(true);
}
this.setState(
{
isChecked: !this.state.isChecked
},
() => {
this.props.selectedItems(e);
}
);
}
render() {
const { item } = this.props;
const { isChecked } = this.state;
console.log(this.props.ItemsChecked);
return (
<label>
<input
type="checkbox"
value={item.id}
checked={isChecked}
onClick={this.handleClick.bind(this)}
/>
{item.name}
</label>
);
}
}
function App() {
return <Items />;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Also available as a codesandbox - https://codesandbox.io/s/r44yn2rwm4
Current outstanding issues/extra functionality to be added:
Any help with this would be greatly appreciated, been working on this for a while and exhausted all avenues!
Upvotes: 3
Views: 16568
Reputation: 7059
I have modified @MrCode fiddle to make some more changes as the functionality check/uncheck Select all checkbox based on individual items was missing. So if individual items are checked/unchecked it should reset "Select All" state as well.
if (checked) {
const collection = this.getAllItems();
this.setState(prevState => ({
checkedListAll: [...prevState.checkedListAll, value * 1],
ItemsChecked: collection.length === prevState.checkedListAll.length + 1
}));
} else {
this.setState(prevState => ({
checkedListAll: prevState.checkedListAll.filter(item => item != value),
ItemsChecked: false
}));
}
Upvotes: 3
Reputation: 64536
I have corrected the codesandbox with quite a few changes. It's best if you compare with yours to see all the changes but in summary:
Manage the checkboxes checked state higher up in the Items
componenet instead of a checkbox handling its own state. Pass the state down to it as props and pass it the change event handler as well.
When select all is clicked, you want to put all of your item ids in the checkedListAll
array in the state instead of toggling them.
This is a key change which handles the checkbox change event. When checked, a new array is created with the existing items plus the new item. When unchecked, a new array is created by .filter()
which filters out the item to be removed.
handleCheckboxClick(e) {
const { value, checked } = e.target;
if (checked) {
this.setState(prevState => ({
checkedListAll: [...prevState.checkedListAll, value * 1]
}));
} else {
this.setState(prevState => ({
checkedListAll: prevState.checkedListAll.filter(item => item != value)
}));
}
}
Upvotes: 5
Reputation: 27
I would check your Property Types. Based on the CodeSandbox you linked, notice how when you click on Select all and then individually select the check boxes, there looks to be Numbers from the 'Select All' and Strings coming from the Individual checkboxes.
Upvotes: 0