3

So this is how my data looks:

categories = [
  {
    name: ""
    products: [
      {
        id: 1,
        ...
      },
      {
        id: 2,
        ...
      }
    ]
  },
  {
    name: ""
    products: [
      {
        id: 3,
        ...
      },
      {
        id: 4,
        ...
      }
    ]
  },
  ...
]

I want to remove a product with id 1 and this is my code:

categories.map(category => category.products.filter(product => product.id !== 1))

Is this the right code? If so how I create a new array and set it with the new values?

Ori Drori
  • 183,571
  • 29
  • 224
  • 209
Unknown
  • 819
  • 9
  • 17
  • 1
    have you tried it? does it work? if not, what specific error message do you receive? – Dan O Apr 08 '20 at 14:05
  • Looks good. Should work but as @DanO mentioned, you need to try it first. – VPaul Apr 08 '20 at 14:06
  • 1
    @VPaul - No, it's replacing `category` objects with (filtered) `products` arrays. – T.J. Crowder Apr 08 '20 at 14:08
  • @DanO that is what I am struggling with. I didnt know how to create a new array with the new values but fortunately T.J Crowder helped out. Thanks anyway – Unknown Apr 08 '20 at 14:13
  • My bad, you're returning products from the filter function and then passing to the map. I guess the answer below handles that case. – VPaul Apr 08 '20 at 14:16

2 Answers2

6

You're close, but your code is replacing category objects with just their filtered products array, losing the rest of the object properties. You need to copy each category object as well as its products array, which you typically do via spread notation:

updatedCategories = categories.map(category => ({
    ...category,
    products: category.products.filter(product => product.id !== 1)
}));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

Just a modified version on T.J. Crowder's response.

The filterGroupedBy function is more versatile and de-coupled from the business logic.

I even created a lodash mixin that you can call via:

  • _.filterGroupedBy(list, key, fn) or
  • _.chain(list).filterGroupedBy(key, fn).value()

Demo

const disp = (value) => console.log(JSON.stringify(value))

const filterGroupedBy = (list, key, fn) => 
  list.map(item => ({
      ...item,
      [key] : item[key].filter(entry => fn(entry))
  }))
  
_.mixin({
  'filterGroupedBy' : (list, key, fn) => filterGroupedBy(list, key, fn)
})

let categories = [{
  name: "",
  products: [{ id: 1 }, { id: 2 }]
}, {
  name: "",
  products: [{ id: 3 }, { id: 4 }]
}]

// plain js
disp(filterGroupedBy(categories, 'products', (prod) => prod.id !== 1))

// lodash - static and chain
disp(_.filterGroupedBy(categories, 'products', (prod) => prod.id !== 1))
disp(_.chain(categories).filterGroupedBy('products', (prod) => prod.id !== 1).value())
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Nested Data

If you have nested data, you can iterate through a path to target the object you want to filter on.

You just need a function similar to Object.byPath (a custom polyfill).

const main = () => {
  let categories = [{
    name: "",
    products: {
      data : [{ id: 1 }, { id: 2 }]
    }
  }, {
    name: "",
    products: {
      data : [{ id: 3 }, { id: 4 }]
    }
  }]

  // plain js
  disp(filterGroupedBy(categories, 'products.data', p => p.id !== 1))
  
  // lodash - static and chain
  disp(_.filterGroupedBy(categories, 'products.data', p => p.id !== 1))
  disp(_.chain(categories).filterGroupedBy('products.data', p => p.id !== 1).value())
}

// Required by filterGroupedBy
if (Object.byPath === undefined) {
  Object.byPath = (obj, path) => path
    .replace(/\[(\w+)\]/g, '.$1')
    .replace(/^\./, '')
    .split(/\./g)
    .reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}

/* #ifndef filterGroupedBy */
const filterGroupedBy = (list, path, fn) => 
  list.map(item => {
      let lastIndex = path.lastIndexOf('.'),
          target = path.substring(0, lastIndex),
          key = path.substring(lastIndex + 1)
      return Object.assign({}, Object.byPath(item, target), {
        [key] : Object.byPath(item, path).filter(entry => fn(entry))
      })
  })

_.mixin({
  'filterGroupedBy' : (list, key, fn) => filterGroupedBy(list, key, fn)
})
/* #endif */

const disp = (value) => console.log(JSON.stringify(value))
main()
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132