简体   繁体   中英

Maintaining react state with a hierarchical object using react hooks (add or update)

I have an a state object in React that looks something like this (book/chapter/section/item):

  const book = {
     id: "123",
     name: "book1",
     chapters: [
      {
        id: "123",
        name: "chapter1", 
        sections: [
          {
           id: "4r4",
           name: "section1",
           items: [
            {
              id: "443",
              name: "some item"
            }
           ]
          }
        ]
      }, 
      {
        id: "222",
        name: "chapter2",
        sections: []
      }
     ]
  }

I have code that adds or inserts a new chapter object that is working. I am using:

// for creating a new chapter:
setSelectedBook(old => {
   return {
     ...old,
     chapters: [
       ...old.chapters, 
       newChapter // insert new object
      ]
   }
})

And for the chapter update, this is working:

setSelectedBook(old => {
    return {
       ...old,
       chapters: [
         ...old.chapters.map(ch => {
           return ch.id === selectedChapterId
             ? {...ch, name: selectedChapter.name}
             : ch
           })
       ]
    }
})

But for my update/create for the sections, I'm having trouble using the same approach. I'm getting syntax errors trying to access the sections from book.chapters. For example, with the add I need:

// for creating a new section:
setSelectedBook(old => {
   return {
     ...old,
     chapters: [
       ...old.chapters,
       ...old.chapters.sections? 
       newSection // how to copy chapters and the sections and insert a new one?
      ]
   }
})

I know with React you're supposed to return all the previous state except for what you're changing. Would a reducer make a difference or not really?

I should note, I have 4 simple lists in my ui. A list of books/chapters/sections/items, and on any given operation I'm only adding/updating a particular level/object at a time and sending that object to the backend api on each save. So it's books for list 1 and selectedBook.chapters for list 2, and selectedChapter.sections for list 3 and selectedSection.items for list 4.

But I need to display the new state when done saving. I thought I could do that with one bookState object and a selectedThing state for whatever you're working on.

Hopefully that makes sense. I haven't had to do this before. Thanks for any guidance.

I think the map should work for this use case, like in your example.

setSelectedBook(old => {
    return {
       ...old,
       chapters: [
         ...old.chapters.map(ch => {
           return { ...ch, sections: [...ch.sections, newSection] }
           })
       ]
    }
})

In your last code block you are trying to put chapters, sections and the new section into the same array at the same level, not inside each other.

Updating deep nested state objects in React is always difficult. Without knowing all the details of your implementation, it's hard to say how to optimize, but you should think hard about different ways you can store that state in a flatter way. Sometimes it is not possible, and in those cases, there are libraries like Immer that can help that you can look in to.

Using the state object you provided in the question, perhaps you can make all of those arrays into objects with id for keys:

const book = {
     id: "123",
     name: "book1",
     chapters: {
      "123": {
        id: "123",
        name: "chapter1", 
        sections: {
          "4r4": {
           id: "4r4",
           name: "section1",
           items: {
            "443": {
              id: "443",
              name: "some item"
            }
           }
          }
        }
      }, 
      "222": {
        id: "222",
        name: "chapter2",
        sections: {},
      }
     ]
  }

With this, you no longer need to use map or find when setting state.

// for creating a new chapter:
setSelectedBook(old => {
   return {
     ...old,
     chapters: {
       ...old.chapters, 
       [newChapter.id]: newChapter
     }
   }
})

// for updating a chapter:
setSelectedBook(old => {
   return {
     ...old,
     chapters: {
       ...old.chapters, 
       [selectedChapter.id]: selectedChapter,
     }
   }
})

// for updating a section:
setSelectedBook(old => {
   return {
     ...old,
     chapters: {
       ...old.chapters, 
       [selectedChapter.id]: {
         ...selectedChapter,
         sections: {
           [selectedSectionId]: selectedSection
         }
       },
     }
   }
})

Please let me know if I misunderstood your problem.

for adding new Section

setSelectedBook( book =>{
   let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )

   selectedChapter.sections=[...selectedChapter.sections, newSection ]

    return {...book}

})

For updating a section's name

setSelectedBook(book=>{
   let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
   let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )

   selectedSection.name = newName

    return {...book}
})

For updating item's name

setSelectedBook(book =>{
   let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
   let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
   let selectedItem = selectedSection.items.find(itm => itm.id === selectedItemId) 

   selectedItem.name = newItemName

    return {...book}
})

I hope you can see the pattern.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM