skip to Main Content

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 }))

2

Answers


  1. 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]);
    
    Login or Signup to reply.
  2. Drew nicely pointed out why your existing attempt doesn’t work. If you want to merge your first state (categories), so that it includes the items with the respective categories from your items state, then you can first make your items state a look-up map which maps the items category to the item object, and then map through your categories state by taking the entries of it and calling .map() to added the items to each object. You can then call Object.fromEntries() to convert it back into an object:

    const categories = { category1: { items: [] }, category2: { items: [] } };
    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 itemsMap = Map.groupBy(items, o => o.itemInfo.category);
    const merged = Object.fromEntries(Object.entries(categories).map(([key, val]) => [
      key,
      {...val, items: val.items.concat(itemsMap.get(key))}
    ]));
    console.log(merged);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search