reactjstypescriptreact-native

Combining two states into one, and sorting the contents into nested objects


I have two states one has categories and the other has items.

all i need is one final state that combines both states, either by merging one into the other or by making a third state.

the categories state looks like this:

{
category1: { items: [] },
category2: { items: [] }
}

and the second state is an array of items and it looks like this:

[
{"id": "1", itemInfo: { name: "itemName", category: "category1" }},
{"id": "2", itemInfo: { name: "itemName", category: "category1" }},
{"id": "3", itemInfo: { name: "itemName", category: "category2" }},
{"id": "4", itemInfo: { name: "itemName", category: "category1" }},
{"id": "5", itemInfo: { name: "itemName", category: "category2" }},
{"id": "6", itemInfo: { name: "itemName", category: "category2" }}
]

i need it to be like this:

{
category1: { items: [
    {"id": "1", itemInfo: { name: "itemName", category: "category1" }},
    {"id": "2", itemInfo: { name: "itemName", category: "category1" }},
    {"id": "4", itemInfo: { name: "itemName", category: "category1" }}
]},

category2: { items: [
    {"id": "3", itemInfo: { name: "itemName", category: "category2" }},
    {"id": "5", itemInfo: { name: "itemName", category: "category2" }},
    {"id": "6", itemInfo: { name: "itemName", category: "category2" }}
]}
}

help. please.

this is what i tried and didn't work, it either duplicates the categories and i end up with 3 "category1" and 3 "category2" or the new items overwrite previous items.

const newFinalList = items.map((item: any) => {
    return {[item.itemInfo.category]: {items: [item]}}
}) 

setFinalState((prevObj: any) => ({ ...prevObj, ...newFinalList }))

Solution

  • Issue

    The issue is that the array mapping returns a new array, but you are wanting to map the array to the categories state shape which is an object. The mapping logic returns an object for each item element in the source array.

    const newFinalList = items.map((item: any) => {
      return {                                      // <-- new object for each item
        [item.itemInfo.category]: { items: [item] } // <-- only one item in each items array
      }
    })
    

    Solution

    Reduce the array into an object.

    Example:

    // Iterate the items array and reduce into an object
    const newFinalList = items.reduce((categories, item) => {
      // If the item category object doesn't exist, create it
      // with an initially empty items array
      if (!categories[item.itemInfo.category]) {
        categories[item.itemInfo.category] = {
          items: [],
        };
      }
    
      // Push the current item into the items array
      categories[item.itemInfo.category].items.push(item);
    
      // return the categories object
      return categories;
    }, {});
    

    const items = [
      { id: "1", itemInfo: { name: "itemName", category: "category1" } },
      { id: "2", itemInfo: { name: "itemName", category: "category1" } },
      { id: "3", itemInfo: { name: "itemName", category: "category2" } },
      { id: "4", itemInfo: { name: "itemName", category: "category1" } },
      { id: "5", itemInfo: { name: "itemName", category: "category2" } },
      { id: "6", itemInfo: { name: "itemName", category: "category2" } },
    ];
    
    const newFinalList = items.reduce((categories, item) => {
      if (!categories[item.itemInfo.category]) {
        categories[item.itemInfo.category] = {
          items: [],
        };
      }
      categories[item.itemInfo.category].items.push(item);
      return categories;
    }, {});
    
    console.log({ newFinalList });

    If you are using this to merge some passed props/context values, it is a React anti-pattern to also store this derived "state" into React state. Either compute and use the value directly, or use the React.useMemo hook to compute and provide a stable reference value.

    const newFinalList = React.useMemo(() => items.reduce((categories, item) => {
      if (!categories[item.itemInfo.category]) {
        categories[item.itemInfo.category] = {
          items: [],
        };
      }
    
      categories[item.itemInfo.category].items.push(item);
      return categories;
    }, {}), [items]);