Search code examples
javascriptreactjssetstate

Forming the components based on the array of objects


For a setting page for an application, I have implemented a slider that has enabled(green) or disabled(red) state. And parent's settings are calculated based on the values of its children.

//Getting the switches configuration inside componnetDidMount something like this

var obj = [
  {
    parent_header_name: "parent1",
    children_info: [
      {
        child_switch_name: "child1",
        isEnabled: true
      },
      {
        child_switch_name: "child2",
        isEnabled: false
      }
    ]
  },
  {
    parent_header_name: "parent2",
    children_info: [
      {
        child_switch_name: "child3",
        isEnabled: true
      }
    ]
  },
  {
    parent_header_name: "parent3",
    children_info: [
      {
        child_switch_name: "child4",
        isEnabled: false
      }
    ]
  }
];

Now based on this value, I need to form a grouping of parent and children something like this:

Label(the value should be parent_header_name) : Parent Switch Component
Label for children(children_switch_name) : Child Switch Component

Also on change of individual children switches toggling I need to get the info of that switch something like this:

For Example, Changing parent1's child1's to disabled

[
  {
    parent_header_name: "parent1",
    children_info: [
      {
        child_switch_name: "child1",
        isEnabled: false
      }
    ]
  }
];

In case parent1 turned to enabled I need to get all its chidren value

[
  {
    parent_header_name: "parent1",
    children_info: [
      {
        child_switch_name: "child1",
        isEnabled: true
      },
      {
        child_switch_name: "child2",
        isEnabled: true
      }
    ]
  }
]

And when parents switch is toggled(when the parent is enabled, children will be enabled and when disabled children will be disabled;), I need to get the entire info of that parent

Also, I need to avoid toggling to "partial" state, the parent should only be enabled or disabled. "Partial" is only representational

For this, I am using react-multi-toggle for this toggle switch.

I have tried something like this: https://codesandbox.io/s/parent-child-switches-gxfx6


Solution

  • You can refactor your Setting component to allow it to renders your switches name and value according to the data received from the API. I will suggest you add an id to each of your switches group, that will ease your work. Here is forked working sandbox. The code can be uptimzed to befit your usecase. The key changes are done in the Setting Component.

    Setting Component Full code

    import React, { Component, Fragment } from "react";
    import isEqual from "lodash.isequal";
    
    import ChildSwitch from "./ChildSwitch";
    import ParentSwitch from "./ParentSwitch";
    import { PARTIAL } from "./constant";
    
    export default class Setting extends Component {
      state = {
        parent: {
          value:
            this.props.children.length > 1
              ? PARTIAL
              : this.props.children[0].isEnabled
        },
        children: this.props.children
      };
    
      componentDidMount() {
        this.setParentSwitchValue();
      }
    
      shouldComponentUpdate(nextProps, nextState) {
        return !isEqual(this.state, nextState);
      }
    
      setChildSwitchValue = (id, isEnabled) => {
        let clickedChild;
        this.setState(
          prevState => ({
            ...prevState,
            children: prevState.children.map(child => {
              if (child.id === id) {
                clickedChild = { ...child, isEnabled: isEnabled };
                return clickedChild;
              } else {
                return child;
              }
            })
          }),
          () => this.setParentSwitchValue(clickedChild)
        );
      };
    
      setParentSwitchValue = clickedChild => {
        const { children } = this.state;
        let parentVal = PARTIAL;
    
        if (children.every(({ isEnabled }) => isEnabled === true)) {
          parentVal = true;
        }
        if (children.every(({ isEnabled }) => isEnabled === false)) {
          parentVal = false;
        }
    
        this.setState(
          prevState => ({
            ...prevState,
            parent: {
              value: parentVal
            }
          }),
          () => {
            this.handleChange();
            if (clickedChild) {
              const changed = {
                parent: {
                  name: this.props.name,
                  value: parentVal
                },
                child: clickedChild
              };
              console.log("This is the changed child", changed);
            }
          }
        );
      };
    
      setChildrenValue = value => {
        this.setState(
          prevState => ({
            ...prevState,
            parent: {
              value
            },
            children: prevState.children.map(child => ({
              ...child,
              isEnabled: value
            }))
          }),
          this.handleChange
        );
      };
    
      handleChange = () => {
        const { id, onChange } = this.props;
        onChange(id, this.state);
      };
    
      handleParentClick = parentVal => {
        if (parentVal !== PARTIAL) {
          this.setChildrenValue(parentVal);
        }
      };
    
      render() {
        const { parent, children } = this.state;
        const { name } = this.props;
        return (
          <div className="boxed">
            <span>{name}</span>
            <ParentSwitch
              childrenCount={children.length}
              parentSwitch={parent.value}
              onSelect={this.handleParentClick}
            />
            {children.map(({ id, name, isEnabled }) => (
              <Fragment key={id}>
                <span>{name}</span>
                <ChildSwitch
                  switchName={id}
                  selected={isEnabled}
                  onSelect={this.setChildSwitchValue}
                />
              </Fragment>
            ))}
          </div>
        );
      }
    }