1

I have been working with React Hooks for a while, but the biggest problem for me is working with arrays.

How to update value of nested array of objects? I want to change dropdown type. My code is below and changes the state directly. How can I fix this? What is the most correct code?

Thanks for your help.

  const [grups, setGrups] = useState([
    {
      id: 1,
      css: "col",
      components: [
        { id: 1, type: "textbox" },
        { id: 2, type: "integer" },
      ],
    },

    {
      id: 2,
      css: "col",
      components: [
        { id: 1, type: "numerictextbox" },
        **{ id: 2, type: "dropdown" },**
      ],
    },
  ]);


 function handleClick(gindex,cindex) {
    const newgrups = [...grups];
    newgrups[gindex] = {...grups[gindex] ,components: [...grups[gindex].components]};
    newgrups[gindex].components[cindex].tip="datetime";
    setGrups(newgrups);

  }
3
  • you might need to use immer library to work with immutable data immerjs.github.io/immer/docs/introduction Commented Jun 25, 2020 at 16:10
  • It's a very nested structure, but I think you're pretty close. Change newgrups[gindex].components[cindex].tip="datetime"; -> to -> newgrups[gindex].components[cindex] = {...newgrups[gindex].components[cindex], tip: "datetime"}; to avoid mutating that last nested object. Commented Jun 25, 2020 at 16:17
  • It looks like you probably need to graduate to useReducer or Redux, or Mobx. Whatever works best for you. Right now every time one item in the array is updated the entire state is updates, and you just need to update one item. IMO, I think 99% of the time useReducer will cover you. Commented Jun 25, 2020 at 17:06

2 Answers 2

1

So you need something like

function handleClick(gindex, cindex) {
  // use map to create a new array and at the same time
  // modify the contents when required
  const newgrups = grups.map((grup, gidx) => {
    // if the index is not the one we want return the while grup as is
    if (gidx !== gindex) return grup;
    // otherwise create a new one by spreading the existing
    return {
      ...grup,
      // and override the prop which is changed
      components: grup.components.map((component, cidx) => {
        // again if the component is not the one we want to update
        // return it as is
        if (cidx !== cindex) return component;
        // otherwise create a new one by spreading the existing
        // and adding/modifying the props we want
        return {
          ...component,
          tip: 'datetime'
        }
      })
    }
  });

  setGrups(newgrups);
}

If you just want to go with the code you have, you just need to create a new component as well

newgrups[gindex].components[cindex] = { ...newgrups[gindex].components[cindex],
  tip: 'datetime'
}
Sign up to request clarification or add additional context in comments.

Comments

0

It is good practice to design your state as you would design a database. In a database normalised design is recommended because you can't just nest states in other states. In your case you could split the state in the same way:

grups

[
    {
      id: 1,
      css: "col"
    },

    {
      id: 2,
      css: "col"
    }
];

components

[
        { grups_id: 1, id: 1, type: "textbox" },
        { grups_id: 1, id: 2, type: "integer" },
        { grups_id: 2, id: 1, type: "numerictextbox" },
        { grups_id: 2, id: 2, type: "dropdown" }
]

This makes it significantly easier to handle the state and avoid nesting and complex procedues. You can easily work with components by applying componenents.filter() and filter by grups_id.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.