0

I'm currently building an ecommerce shop app for my portfolio and I'm in this following situation.

User selects an item and adds it to cart and they add the same item to the cart again. Now when they want to remove one item all the same items are being removed. I'm currently using filter method and obviously filter method is doing it's job.

Can anyone please tell me how I can remove one product from my cart, without removing all of its type?

Thank you for your time.

Here is the sample of the array which I want to filter.

selectedProducts = [
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: photo1,
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 13,
  sku: 51498472915966370,
  title: "Dark Thug Blue-Navy T-Shirt",
  availableSizes: ["M"],
  style: "Front print and paisley print",
  price: 29.45,
  imgUrl: photo2,
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: photo1,
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
} ];
Adi
  • 164
  • 1
  • 11
  • 2
    Recommend you actually add a cartId to the items as you add them, then use that as your filter. – Bibberty Mar 26 '19 at 21:25
  • 1
    Possible duplicate of [Remove duplicates from an array of objects in JavaScript](https://stackoverflow.com/questions/2218999/remove-duplicates-from-an-array-of-objects-in-javascript) – Script47 Mar 26 '19 at 21:27
  • I would say this is not a duplicate, as there is a difference between `removing a duplicate` and `preventing duplicates from being removed`. – Webber Mar 26 '19 at 21:30
  • While adding your product to the cart set another id for it which is the item product within the cart and not filter the actual id while it is shared between more than one item – Osama Mar 26 '19 at 21:36

5 Answers5

1

You can use Array.prototype.reduce when trying to filter out multiple items.

This will return an object with an array for each item selected. The length of the arrays will be the number of items of each id.

let items = selectedProducts.reduce((a,v) => {
  a[v.id] ?  a[v.id].push(v) : a[v.id] = [v];
  return a;
}, {});

more compact version:

selectedProducts.reduce((a,v)=>(a[v.id]?a[v.id].push(v):a[v.id]=[v],a),{});

let selectedProducts = [
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: 'photo1',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 13,
  sku: 51498472915966370,
  title: "Dark Thug Blue-Navy T-Shirt",
  availableSizes: ["M"],
  style: "Front print and paisley print",
  price: 29.45,
  imgUrl: 'photo2',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: 'photo1',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
} ];

let items = selectedProducts.reduce((a,v) => {
a[v.id] ?  a[v.id].push(v) : a[v.id] = [v];
return a;
}, {});

console.log(items);
zfrisch
  • 8,474
  • 1
  • 22
  • 34
1

It looks like you are trying to remove the first occurrence of a an item in the array you have. I would suggest you try something like this using the indexOf and splice methods.

function removeFromCart ( sku ) {
  const index = selectedProducts.indexOf( sku );
  selectedProducts.splice( index, 1 ); 
}

Its preferable to use splice and indexOf together since javascript will do the removal in-place for you without having to create an accumulator (when you use reduce). the code is also simpler and easier to read.

Time Complexity - O(n) Space Complexity - O(1)

You could use a reducer to solve the problem but you would have to create an object to store your result increasing the space complexity to O(n)

let selectedProducts = [
    {
      id: 12,
      sku: 12064273040195392,
      title: "Cat Tee Black T-Shirt",
      availableSizes: ["XS", "S"],
      style: "Black with custom print",
      price: 10.9,
      imgUrl: 'photo1',
      currencyId: "GBP",
      currencyFormat: "£",
      isFreeShipping: true
    },
    {
      id: 13,
      sku: 51498472915966370,
      title: "Dark Thug Blue-Navy T-Shirt",
      availableSizes: ["M"],
      style: "Front print and paisley print",
      price: 29.45,
      imgUrl: 'photo2',
      currencyId: "GBP",
      currencyFormat: "£",
      isFreeShipping: true
    },
    {
      id: 12,
      sku: 12064273040195392,
      title: "Cat Tee Black T-Shirt",
      availableSizes: ["XS", "S"],
      style: "Black with custom print",
      price: 10.9,
      imgUrl: 'photo1',
      currencyId: "GBP",
      currencyFormat: "£",
      isFreeShipping: true
    } ];

    const index = selectedProducts.indexOf( 12064273040195392 );
    selectedProducts.splice( index, 1 );

    console.log(selectedProducts);
0

May be you can find the index of the first item in array and remove the item from array based on the index. An example:

var selectedProducts = [
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: 'photo1',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 13,
  sku: 51498472915966370,
  title: "Dark Thug Blue-Navy T-Shirt",
  availableSizes: ["M"],
  style: "Front print and paisley print",
  price: 29.45,
  imgUrl: 'photo2',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
},
{
  id: 12,
  sku: 12064273040195392,
  title: "Cat Tee Black T-Shirt",
  availableSizes: ["XS", "S"],
  style: "Black with custom print",
  price: 10.9,
  imgUrl: 'photo1',
  currencyId: "GBP",
  currencyFormat: "£",
  isFreeShipping: true
} ];


const index = selectedProducts.findIndex(obj => obj.id == 12);
selectedProducts.splice(index, 1);
console.log(selectedProducts);
Vishnu
  • 897
  • 6
  • 13
0

You need a unique identifier for each item in the cart.

For Example, when you add 'newItem' to the card:

newItem['uniqueItemId'] = {UniqueIdentifier};
selectedProducts.push(newItem);

This will give you access to uniqueItemId on the object, and you can now filter by this value so as to only remove the desired item:

selectedProducts = selectedProducts.filter(item => item.uniqueItemId !== {UniqueIdentifier});
jkrat
  • 158
  • 8
0

Slightly long answer here, but it shows how we might assign a unique cart ID and then use that to filter when we remove.

For the demo, the cart ID is simply a Date.now() which is a value is millisecs so should be unique for this.

We assign the unique ID during our add to cart: NOTE: We copy the product.

const addProductToCart = (id) => {
  let product = Object.assign({}, products.find(p => p.id == id));
  product.cartId = Date.now();**
  cart.push(product);
  renderCart();
};

Now when you click an item in the cart (remove) we call the remove method: NOTE: We use the cart ID to reference the item and NOT the product ID

const removeFromCart = (cartId) => {
  cart = cart.filter(item => item.cartId != cartId);
  renderCart();
};

Below is a working snippet, showing the principles working.

let products = [
  {
    id: 1,
    name: "product one"
  },
  {
    id: 2,
    name: "product two"
  },
  {
    id: 3,
    name: "product three"
  }
];

let cart = [];

const renderProduct = (product) => `<li class="product" data-id="${product.id}">${product.name}</li>`;

const renderCartItem = (cartItem) => `<li class="cartItem" data-cart-id="${cartItem.cartId}">${cartItem.name}</li>`;

const renderCart = () => {
  document.querySelector('.cart').innerHTML = cart.map(c => renderCartItem(c)).join('');
};

const addProductToCart = (id) => {
  let product = Object.assign({}, products.find(p => p.id == id));
  product.cartId = Date.now();
  cart.push(product);
  renderCart();
};

const removeFromCart = (cartId) => {
  cart = cart.filter(item => item.cartId != cartId);
  renderCart();
}

document.addEventListener('DOMContentLoaded', () => {
  document.querySelector('.items').innerHTML = products.map(p => renderProduct(p)).join('');
});

document.addEventListener('click', (e) => {
  if(e.target.matches('.product')) {
    addProductToCart(e.target.dataset.id);
  }

  if(e.target.matches('.cartItem')) {
    removeFromCart(e.target.dataset.cartId);
  }

});
<ul class="items"></ul>
    <ul class="cart"></ul>

Some notes on object reference and why we must copy our object when moving from product list to cart list.

In this first snippet we add without any copy of the object. NOTE: that even though we add property to cart[0] it actually adds to all. Because it is a reference and actually the array is just pointers to the same object.

let products = [
  { 
    id: 1,
    name: "one"
  },
  { 
    id: 1,
    name: "one"
  }  
];

let cart = [];

// Add without copy
for(let i=0; i< 3; i++) {
  cart.push(products[0]);
}

// now add an attribute to one cart item
cart[0].cartId = 1;

cart.forEach(item => console.log(item));

so we need to copy our object, here using Object.assign. This creates new instances and so each pointer points to a different object

let products = [
  { 
    id: 1,
    name: "one"
  },
  { 
    id: 1,
    name: "one"
  }  
];

let cart = [];

// Add without copy
for(let i=0; i< 3; i++) {
  cart.push(Object.assign({}, products[0]));
}

// now add an attribute to one cart item
cart[0].cartId = 1;

cart.forEach(item => console.log(item));

Change your add to cart to this:

addCartItems = data => {
  copyData = Object.assign({}, data);
  copyData.cartId = Date.now();
  this.setState({
    cartItems: [...this.state.cartItems, copyData]
  });
};
Bibberty
  • 4,670
  • 2
  • 8
  • 23
  • Thank you for your reply. I'm sorry i didn't mention I'm using React. because of this even milliseconds are not enough unique to remove the item. in the snippet it works fine but in my react app it removes them both. – Adi Mar 26 '19 at 22:38
  • Remember the milliseconds is when the user adds to cart. Not when cart is rendered. – Bibberty Mar 26 '19 at 22:39
  • Does that make sense? When the user does an add to cart. You push a product from your product list into your cart array and as you do this you add the `cartId` property. – Bibberty Mar 26 '19 at 22:45
  • Yes i'm doing exactly like you said here is my code: addCartItems = data => { data.cartId = Date.now(); this.setState({ cartItems: [...this.state.cartItems, data] }); }; – Adi Mar 26 '19 at 22:46
  • What is data. You need to ensure it is a copy. Notice my `Object.assign`. if `data` is an object direct from your product array it will not work. – Bibberty Mar 26 '19 at 22:47
  • data is the whole object item. – Adi Mar 26 '19 at 22:50
  • Ok, that is a problem. It you directly add an item from you product list without copying it you are referencing the same object. So setting `data.cartId = aValue` will cause all references in your cart to the same object to change. – Bibberty Mar 26 '19 at 22:52
  • Ah I see the problem now. Can you please tell me how I can copy the data and reference it later? I'm bit confused about it – Adi Mar 26 '19 at 22:54
  • I just added to the answer, first snippet shows the problem, second shows it fixed. Use `Object.assign({}, objectToCopy)` – Bibberty Mar 26 '19 at 23:00
  • I understood a little bit and I'm more confused about it now. I don't know where to add this logic inside my addCartItems function? I'm really sorry I'm terribly confused – Adi Mar 26 '19 at 23:15
  • Take a look, I added to end of answer. – Bibberty Mar 26 '19 at 23:18
  • Thank you very much. I understood now exactly what you meant. I'm really sorry that you had to explain every step. I learned a new lesson from you. Thanks once again. – Adi Mar 26 '19 at 23:24
  • Happy coding !! – Bibberty Mar 26 '19 at 23:27