0

in my react app, there is a setting "page" with different setting categories with respective setting items. Hence, I need a nested state (?) to avoid having lots of properties called "category1-id", "category1-element1-id" etc.

That looks as follows:

    this.state = {
          categories: [
            {
              id: 1,
              label: "Category 1",
              elements: [
                { id: 1, label: "Option 1", selected: true },
                { id: 2, label: "Option 2", selected: false },
              ],
            },
            {
              id: 2,
              label: "Category 2",
              elements: [
                { id: 1, label: "Option 1", selected: true },
                { id: 2, label: "Option 2", selected: false },
                { id: 3, label: "Option 3", selected: false },
              ],
            },
          Other property: "...",

To update the state, I need a deep copy instead of a shallow one. How do I achieve this? Is there any other alternative solution?

Using

let categories = [...this.state.categories];
categories = JSON.parse(JSON.stringify(categories));

doesn't work (error) and is not really fail-proof.

Since this is the only nested state in my app, I'd like to avoid using lodash & co.

The function called when clicking on the option looks like this. But this modifies the state directly.

  handleOptionSelect = (categoryId, option) => {
    const categories = [...this.state.categories];

    // FIND INDEX OF CATEGORY
    const category = categories.find((category) => category.id === categoryId);
    const indexCat = categories.indexOf(category);

    // //FIND INDEX OF ELEMENT
    const elements = [...category.elements];
    const indexEle = elements.indexOf(element);

    //MODIFY COPIED STATE
    const e = categories[indexCat].elements[indexEle];
    e.selected = e.selected ? false : true;

    // //SET NEW STATE
    this.setState({ categories });
  }

Thank you very much for your help! I am still at the beginning trying to learn React and JS :)

John
  • 23
  • 4
  • You know you can import only specific lodash functions, either by installing just one specific package or importing a specific function from the whole library? If you don't want to do that, you'll just end up recreating the same functionality in your own code... ```import cloneDeep from "lodash/cloneDeep"``` or https://www.npmjs.com/package/lodash.clonedeep. If you still want to rather write your own, here's an answer that lists all the options: https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript – Thomas Kuhlmann Apr 07 '21 at 16:51
  • Thanks, I did not know that (I am quite new to programming)! Actually, I struggle a bit in implementing your solution. I did use ```npm i lodash.clonedeep``` in the terminal, added ```import cloneDeep from "lodash/cloneDeep"``` and replaced ```const categories = [...this.state.categories];``` with ```const categories = _.cloneDeep(this.state.category)``` but I get an error saying "'_' is not defined no-undef". What did I do wrong? – John Apr 07 '21 at 18:36
  • Sorry, my bad - the two things I posted were the two different approaches. You'd be using ```import cloneDeep from 'lodash/cloneDeep``` in combination with the whole lodash package (```npm i lodash```). It's still just importing what you specified, but you can import more modules if you need them somewhere else. ```npm i lodash.clonedeep``` installs only the standalone cloneDeep function, you simply use it by importing ```import cloneDeep from "lodash.cloneDeep"```. When you want to use it, you don't nee the leading ```_.``` since you imported the function directly, not under a namespace – Thomas Kuhlmann Apr 08 '21 at 09:04

1 Answers1

0

What about if you use an object to store your categories? I mean, use a hash of key values in which each key is the id of the category.

this.state = {
      categories: {
        1: {
          label: "Category 1",
          elements: [
            { id: 1, label: "Option 1", selected: true },
            { id: 2, label: "Option 2", selected: false },
          ],
        },
        2: {...}
      }
}

This should help you add or update any category, for example:

// Update category of id 4
this.setState({...categories, 4: updatedCategory })

Same principle should apply to the elements of the category if you use an object to store them.

diegocl02
  • 2,688
  • 2
  • 9
  • 10
  • Thanks! In that case, I cannot use functions such as .map to render the categories, .filter to only pass the selected options to the "execution" component, can I? – John Apr 07 '21 at 17:15
  • I think you can still do that by using `Object.keys`. Something like Object.keys(categories).map(key => categories[key]) – diegocl02 Apr 07 '21 at 19:23