Jeremy Dixon
Jeremy Dixon

Reputation: 101

Best way to handle complex State in React

I am attempting to create a react application which takes the following data structure to render the application...

 var cellTest = [
    {
      type: 'group',
      children: [
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
        {type: 'Cell', name: 'Constitution', abbreviation: 'CON', value: 10},
        {type: 'Cell', name: 'Intelligence', abbreviation: 'INT', value: 10},
        {type: 'Cell', name: 'Wisdom', abbreviation: 'WIS', value: 10},
        {type: 'Cell', name: 'Charisma', abbreviation: 'CHA', value: 10},
      ]
    },
    {
      type: 'group',
      children: [
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
        {type: 'Cell', height: 2, width: 2, name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', height: 2, width: 2, name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', height: 2, width: 2, name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', height: 2, width: 3, name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
      ]
    },
    {
      type: 'group',
      children: [
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
      ]
    },
    {
      type: 'group',
      children: [
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
        {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
      ]
    }, 
  ]

Currently, this object is passed to a 'Sheet' component which iterates through the array and renders a 'Group' component passing all data down as props. The group component then iterates through the children array and renders a Cell component with the corresponding props. Eventually each cell will store values that must be bound to this data structure. I got this working fine with dummy data being passed down, however, when I decided to try to hook up state ( which I have little experience with ) everything blew up in my face.

If I click on a Cell component and change a value I hoped to send a message to the parent component which would house the 'Sheet' in State. That message would alter the state which would trigger a re-rendering ( I *think) of the component which cascade the changes down. However, when I did this I got some weird errors and upon googling it seems that react cannot handle state with nested data?

What is the best way to approach state management in this situation? Should I flatten my data somehow?

I apologize if this question is a bit vague, however, I'm newish to react and having my mind blown :)

UPDATE:

I attempted to, as a test, simply add a new group to the state but it breaks.

 let newGroup =     {
    type: 'group',
    children: [
      {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
      {type: 'Cell', name: 'Strength', abbreviation: 'STR', value: 10},
      {type: 'Cell', name: 'Dex', abbreviation: 'DEX', value: 10},
      {type: 'Cell', name: 'Constitution', abbreviation: 'CON', value: 10},
      {type: 'Cell', name: 'Intelligence', abbreviation: 'INT', value: 10},
      {type: 'Cell', name: 'Wisdom', abbreviation: 'WIS', value: 10},
      {type: 'Cell', name: 'Charisma', abbreviation: 'CHA', value: 10},
    ]
  }

  const [sheet, setSheet] = useState(cellTest);

  function addGroup(){
    setSheet(prevSheet => prevSheet.push(newGroup))
  }

This produces the following errors

TypeError: props.sheet.map is not a function

   8 | console.log('BOOM', props.sheet)
>  9 | let sheet = props.sheet.map((group, index)=>{
     | ^  10 |     // Render each group and pass data within the group as props to that group
  11 |     return <Group key={'Group'+index} {...props.sheet[index]}/>
  12 | })

And in my console, I can see that the value of my props.sheet ( as read by the component which my state is passed down to ) is now the integer 5 for some reason. I even logged the value of the sheet being passed down before it hit the component and it was okay until the component tried to read it. The only thing that I can think of is that react does not know how to update my components because of the nested state?

Upvotes: 2

Views: 794

Answers (1)

Luke Storry
Luke Storry

Reputation: 6752

Assign an ID to each cell.

Then write a method in your parent element that can take a cell ID & value, and then create a new state object by copying all the unchanged data and merging it with new objects containing the new value for the given cell.

Pass that method through as a callback to each cell, and call that to update the state upon change.

You can use a library like immutability-helper to aid with the deep-copying.

EDIT: Regarding your update, you're on the right lines, but the push() method adds elements to the end of the array and returns the new length of the array. Also you can't just add to an existing object, you need to create a new object, or React will not spot the change and will not re-render.

Try:

addGroup = () => setSheet(prevSheet => [...prevSheet, newGroup]

Upvotes: 3

Related Questions