0
votes

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;
1

1 Answers

1
votes

when you updating the state in onChildChange and onParentChange method you are referencing the same state. You can check sandbox for fix: https://codesandbox.io/s/loving-moon-0e91c

Check onChildChange and onParentChange method inside CheckBoxGroup.js file