Trying to set an array with unique values using filter is failing

Viewed 76

I am trying to merge 2 arrays which might contain some different keys from one array to the other.

The thing is that it is not filtering the already-merged-array as it should. I need it to be with unique values.

These are the 2 arrays:

const arr1 = [
  {
    id: "1",
    title: "My Name"
  },
  {
    id: "2",
    title: "Your Name"
  },
  {
    group: [
      {
        id: "4",
        title: "Some Agreement"
      },
      {
        id: "5",
        title: "Some Beer"
      }
    ]
  }
];

const arr2 = [
  {
    id: "1",
    title: "Company Name",
  },
  {
    id: "2",
    title: "A title",
  },
  {
    id: "3",
    title: "Question Group 1",
  },
  {
    id: "4",
    title: "Confidentiality Agreement",
  },
  {
    id: "5",
    title: "Some more",
  },
  {
    id: "6",
    title: "Ayo",
  }
];

The first array sometimes could contain that key named group which is a mix of 2 or more items of the same type as the objects in the core array ({ id: string, title: string }).

The first array is conformed by the items in the second array.

Right now I need to send all of the items of the arr2 to the arr1 without removing those items from the arr2, but I need to exclude from that merge the existing items/ids which also have to be tested against the items within the group key/array. Do you get it?

This should be the final result of arr1 :

const arr1 = [
  {
    id: "1",
    title: "My Name"
  },
  {
    id: "2",
    title: "Your Name"
  },
  {
    group: [
      {
        id: "4",
        title: "Some Agreement"
      },
      {
        id: "5",
        title: "Some Beer"
      }
    ]
  },
  {
    id: "3",
    title: "Question Group 1",
  },
  {
    id: "6",
    title: "Ayo",
  }
]

As you see there, the new items must be added to the end of the array, the position should persist.

I created a reproducible example: https://codesandbox.io/s/determined-beaver-r5t6v?file=/src/App.js

And the relevant piece of code:

  const handleClick = () => {
    setMerge((qs) => {
      const merged = [...qs, ...qs2];

      const unique = merged.filter((v, i, a) => {
        return v.id === a[i].id;
      });

      return unique;
    });
  };

I got up to there because I can't even filter the array by unique values based on their id.

The thing is that this function could be developed different. I just need to do not allow repeated items in the arr2, including the ones in the group array.

3 Answers

Below is one of the ways in order to achieve the desired output.

  • First Create a Map with ids which are present in arr1
  • Filter arr2 where the id of the object that is not present in ids Map created in step 1
  • Concatenate both arr1 and the filtered array in the step 2

const arr1 = [{id:"1",title:"My Name"},{id:"2",title:"Your Name"},{group:[{id:"4",title:"Some Agreement"},{id:"5",title:"Some Beer"}]}];

const arr2 = [{id:"1",title:"Company Name"},{id:"2",title:"A title"},{id:"3",title:"Question Group 1"},{id:"4",title:"Confidentiality Agreement"},{id:"5",title:"Some more"},{id:"6",title:"Ayo"}];

//Create a Map with ids which are present in arr1
const ids = arr1.reduce((res, obj) => {
  if(obj.group) {
    obj.group.forEach(innerObj => {
      res[innerObj.id] = innerObj
    });
  } else {
    res[obj.id] = obj;
  }
  return res;
}, {});
//console.log(ids);

//Filter out the arr2 where the id of an object is not
//present in the Map created above(i.e., ids object)
const filteredItems = arr2.filter(({id}) => !ids[id]);
//console.log(filteredItems);

//Concatenate arr1 and the filteredItems to get the final output
const finalRes = [...arr1, ...filteredItems];
console.log(finalRes);
.as-console-wrapper {
  max-height: 100% !important;
}

Basically it seems you want all of the first array copied into a resultant array, and only those from the second array that don't have a matching id in the first.

This solution utilizes a recursive search on the group property array, with a recursive base case of just checking the id property.

const isContained = (arr, element) => arr.some(el => {
  if (el.group) {
    return isContained(el.group, element); // <-- recurse on group array
  }
  return el.id === element.id // <-- return ids match
});

...

const handleClick = () => {
  setMerge((qs) => {
    const merged = [
      ...qs, // <-- shallow copy first array
      ...qs2.filter((el) => !isContained(qs, el)), // <-- filtered second array
    ];

    return merged;
  });
};

Edit trying-to-set-an-array-with-unique-values-using-filter-is-failing

You can create a Set to keep track of the existing ids from arr1 and only add items from arr2 that don't exist in arr1.

setMerge((qs) => {
  const ids = new Set()
  qs.forEach((item) => {
    if (item.group) {
      item.group.forEach((el) => ids.add(el.id))
      return
    }
    ids.add(item.id)
  })

  const itemsToAdd = qs2.filter((item) => !ids.has(item.id))
  return [...qs, ...itemsToAdd]
})

Edit hungry-forest-1fybc

Related