3

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:

  • Clicking on the 'select all' checkbox, then unticking one of the item checkboxes deselects the rest.
  • Selecting all items individually should tick the 'select all' checkbox
  • Fix 'Selected List' ID collection

Any help with this would be greatly appreciated, been working on this for a while and exhausted all avenues!

1
  • you invert the checked states. do an implicit selectAll / unselectAll for this usecase and don't ItemsChecked: !this.state.ItemsChecked - go for true/false. your current implementation is 'toggle' - not check. Commented Sep 17, 2018 at 15:39

3 Answers 3

5

Working codesandbox

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)
    }));
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Fantastic :) Just the kind of functionality I needed. I've been through your changes and it has helped me understand a bit more where I was going wrong. Thank you very much :)
3

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
    }));
}

Working Codesandbox

1 Comment

Great stuff, was having a go at this myself, as it was the only functionality outstanding but your code works a lot better and is leaner. Thanks :)
0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.