Search code examples
reactjsmaterial-uidropdown

How can I check all child items when the main head is checked and vice-versa?


I am making a dropdown list in which I want to select all the sub-items if the main head is checked, and select the head, if all the sub-items are checked.

const DropDownList = ({ data }) => {
  const [open, setOpen] = useState({});
  const [checked, setChecked] = useState({});

  const handleClick = (department) => {
    setOpen((prevOpen) => ({
      ...prevOpen,
      [department]: !prevOpen[department],
    }));
  };

  const handleCheck = (item) => {
    setChecked((prevChecked) => ({
      ...prevChecked,
      [item]: !prevChecked[item],
    }));
  };

  return (
    <List>
      {data.map((department) => (
        <React.Fragment key={department.department}>
          <ListItem
            sx={{ cursor: "pointer" }}
            onClick={() => handleClick(department.department)}
          >
            <Checkbox
              checked={checked[department.department] || false}
              onChange={() => handleCheck(department.department)}
            />
            <ListItemText primary={department.department} />
            {open[department.department] ? <ExpandLess /> : <ExpandMore />}
          </ListItem>
          <Collapse in={open[department.department]} unmountOnExit>
            <List
              component="div"
              sx={{ padding: "0 35px", cursor: "pointer" }}
              disablePadding
            >
              {department.sub_departments.map((subDepartment) => (
                <ListItem key={subDepartment}>
                  <Checkbox
                    checked={checked[subDepartment] || false}
                    onChange={() => handleCheck(subDepartment)}
                  />
                  <ListItemText primary={subDepartment} />
                </ListItem>
              ))}
            </List>
          </Collapse>
        </React.Fragment>
      ))}
    </List>
  );
};

export default DropDownList;

This is my List component.

I know the logic, but I can't implement it. I don't know how to. I know that by checking if all sub-items are checked, we can check the head too, but I don't know how to implement this bit.


Solution

  • For "select all the sub-items if the main head is checked", consider modifying handleCheck() to pass an item object. This will let us know of the checked item's sub_departments:

    const handleCheck = (item) => {
      setChecked((prevChecked) => ({
        ...prevChecked,
        [item.department]: !prevChecked[item.department],
      }));
    };
    …
    <Checkbox
      …
      onChange={() => handleCheck(department)}
    />
    …
    <Checkbox
      …
      onChange={() => handleCheck({ department: subDepartment })}
    />
    

    If it is being checked, check its sub_departments:

    const handleCheck = (item, parent = {}) => {
      setChecked((prevChecked) => {
        const list = {
          ...prevChecked,
          [item.department]: !prevChecked[item.department],
        };
    
        if (list[item.department]) {
          if (item.sub_departments) {
            item.sub_departments.forEach(sub => {
              list[sub] = true;
            });
          }
        }
    
        return list;
      });
    };
    

    For "select the head, if all the sub-items are checked", pass in the parent department item as a second argument to handleCheck():

    const handleCheck = (item, parent = {}) => {
      …
    }
    …
    <Checkbox
      …
      onChange={() => handleCheck({ department: subDepartment }, department)}
    />
    

    Then check the parent department if all the sub_departments are checked:

    const handleCheck = (item, parent = {}) => {
      setChecked((prevChecked) => {
        const list = {
          ...prevChecked,
          [item.department]: !prevChecked[item.department],
        };
    
        if (list[item.department]) {
          …
          if (parent.sub_departments && parent.sub_departments.length) {
            let checkParent = true;
    
            for (const subItem of parent.sub_departments) {
              if (!list[subItem]) {
                checkParent = false;
                break;
              }
            }
    
            if (checkParent) {
              list[parent.department] = true;
            }
          }
        }
    
        return list;
      });
    };
    

    Full example:

    const { useState } = React;
    const { List, ListItem, Checkbox, ListItemText, Collapse } = MaterialUI;
    
    const ExpandMore = () => 'ExpandMore';
    const ExpandLess = () => 'ExpandLess';
    
    const DropDownList = ({ data }) => {
      const [open, setOpen] = useState({});
      const [checked, setChecked] = useState({});
    
      const handleClick = (department) => {
        setOpen((prevOpen) => ({
          ...prevOpen,
          [department]: !prevOpen[department],
        }));
      };
    
      const handleCheck = (item, parent = {}) => {
        setChecked((prevChecked) => {
          const list = {
            ...prevChecked,
            [item.department]: !prevChecked[item.department],
          };
          
          if (list[item.department]) {
            if (item.sub_departments) {
              item.sub_departments.forEach(sub => {
                list[sub] = true;
              });
            }
     
            if (parent.sub_departments && parent.sub_departments.length) {
              let checkParent = true;
    
              for (const subItem of parent.sub_departments) {
                if (!list[subItem]) {
                  checkParent = false;
                  break;
                }
              }
              
              if (checkParent) {
                list[parent.department] = true;
              }
            }
          }
          
          return list;
        });
      };
    
      return (
        <List>
          {data.map((department) => (
            <React.Fragment key={department.department}>
              <ListItem
                sx={{ cursor: "pointer" }}
                onClick={() => handleClick(department.department)}
              >
                <Checkbox
                  checked={checked[department.department] || false}
                  onChange={() => handleCheck(department)}
                />
                <ListItemText primary={department.department} />
                {open[department.department] ? <ExpandLess /> : <ExpandMore />}
              </ListItem>
              <Collapse in={open[department.department]} unmountOnExit>
                <List
                  component="div"
                  sx={{ padding: "0 35px", cursor: "pointer" }}
                  disablePadding
                >
                  {department.sub_departments.map((subDepartment) => (
                    <ListItem key={subDepartment}>
                      <Checkbox
                        checked={checked[subDepartment] || false}
                        onChange={() => handleCheck({ department: subDepartment }, department)}
                      />
                      <ListItemText primary={subDepartment} />
                    </ListItem>
                  ))}
                </List>
              </Collapse>
            </React.Fragment>
          ))}
        </List>
      );
    };
    
    
    ReactDOM.createRoot(document.getElementById('app')).render(
      <DropDownList
        data={[
          {
            department: 'Fruit',
            sub_departments: ['apple', 'pear', 'orange'],
          },
          {
            department: 'Colors',
            sub_departments: ['Red', 'Green', 'Blue'],
          },
        ]}
      />
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://unpkg.com/@mui/[email protected]/umd/material-ui.development.js"></script>
    
    <div id="app"></div>