I have a product. I add it to my cart. If such an item exists in the shopping cart, I will increase its counter. It's simple, checking by id.
I have a product, it has an array of options. (milk, cream, sugar, etc.) How to check the cart, and find an existing copy of the product with such options?
function addCoffeeToCart(coffee: CartItem) {
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) => cartItem.id === coffee.id
);
const newCart = produce(cartItems, (draft) => {
if (coffeeAlreadyExistsInCart < 0) {
draft.push(coffee);
} else {
draft[coffeeAlreadyExistsInCart].quantity += coffee.quantity;
}
});
setCartItems(newCart);
}
Product:
export interface Coffee {
id: number;
tags: string[];
name: string;
description: string;
photo: string;
price: number;
options: Options[];
}
Options:
export interface Options{
id: number;
name: string;
price: number;
}
Change coffeeAlreadyExistsInCart to this and try again:
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) =>
cartItem.id === coffee.id &&
JSON.stringify(cartItem.options) === JSON.stringify(coffee.options)
);
the simplest way of dealing with the count / if exist - is to use an object literal as a map of the existing items in the cart. Then you can have an array of the items with their salient details, and use the object literal to keep track of the count (which automatically deals with the if-exists issue) and then create a cart and a running total of the toal price etc.
The advantage of this approach is that it separates the count logic from the item details in your store and allows for different id and display... eg you can have an id of "c1234-1" and a displayText of "Coffee" and this will still allow you to have your calculartions. Its also good to separate your items details from the cart / store - so that you can easily update prices or availability etc.
Deleting items from the cart is simple - just decrement the count of the item down to 0 and this will adjust the cart total as you do so.
const items = [
{id: 'coffee', name: 'Coffee', price: 5.5},
{id: 'tea', name: 'Tea', price: 3.5},
{id: 'cocoa', name: 'Cocoa', price: 2.5},
{id: 'milk', name: 'Milk', price: .5},
{id: 'sugar', name: 'Sugar', price: 0},
]
const cartItems = {};
function addItemToCart(id) {
cartItems[id]
? cartItems[id] ++
: cartItems[id] = 1;
setCart()
}
function setCart() {
const cart = {total: 0, items:[]};
Object.keys(cartItems).forEach(id => {
const item = items.find(x => x.id === id);
const itemQty = cartItems[id];
cart.items.push({name: item.name, quantity: itemQty, subTotal: item.price * itemQty });
cart.total+= (item.price * itemQty);
})
console.log(cart)
}
addItemToCart('coffee');
// gives {"total": 5.5, "items": [{"name": "Coffee","quantity": 1, "subTotal": 5.5}]}
addItemToCart('coffee');
// gives { "total": 11, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11}]}
addItemToCart('tea');
// gives { "total": 14.5, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": Tea","quantity": 1,"subTotal": 3.5} ] }
addItemToCart('milk');
// gives { "total": 15, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": "Tea","quantity": 1,"subTotal": 3.5},{"name": "Milk","quantity": 1,"subTotal": 0.5} ]}}
addItemToCart('sugar');
// gives { "total": 15, "items": [{"name": "Coffee","quantity": 2,"subTotal": 11},{"name": "Tea","quantity": 1,"subTotal": 3.5},{"name": "Milk","quantity": 1,"subTotal": 0.5},{"name": "Sugar","quantity": 1,"subTotal": 0} ] }
On the condition of the findIndex you have to add that verification. As answered you can do it with Json.stringify of both arrays but that assumes they are on the same order and complety equal. I prefer to use Array.every and Array.some to ignore those constraints at cost of a little of performance.
const coffeeAlreadyExistsInCart = cartItems.findIndex(
(cartItem) =>
cartItem.id === coffee.id &&
cartItem.options.length === coffee.options.length &&
coffee.options.every(item => cartItem.options.some(elem => elem.id === item.id))
);
Basically with this we are "asking" to each CartItem if they have the same number of options as coffee ( dont know if as to be like that, if you want to just check if have the all the options but not if they have the same number of options you can ignore that condition ) and if every option on the cartItem exists on teh coffee options
Related
I have a list of products like:
listOfProducts = [{name: "prod1", productId: 1}, {name:"prod2", productId: 2},.....]
If I choose a product i'll populate the array
listSelectedProducts
it will contains the choosen products.
And I make a post to save my choise.
Now If I go back in the products page, i'll have an array:
oldProductsArray
which contains the products that I have saved in db.
listSelectedProducts
with my selected products.
Now If I remove the selected product to choose another one, my oldProductsArray will have the first product, but listSelectedProducts will have the new product choose.
So now I should remove from my db the product that I don't want anymore (an it is in oldProductsArray). So I thought to compare (and there I have my problem) the two arrays and if elements in oldProductsArray are not in listSelectedProducts, i'll create a new array with the products not selected to delete them.
So let's do an example:
I'm in my products page and choose a product.
So listSelectedProducts = [{name: "prod1", productId: 1}]
and I will post in db.
I return in products page and this time I have:
listSelectedProducts = [{name: "prod1", productId: 1}]
oldProductsArray = [{name: "prod1", productId: 1}]
I deselect the product: prod1 and choose the product: prod2
So I have:
listSelectedProducts = [{name: "prod2", productId: 2}]
oldProductsArray = [{name: "prod1", productId: 1}]
and now I should check if products in oldProductsArray are also in listSelectedProducts, if they are I can do the post, if they are not (like in this case) I should remove from db the product.
So I have a function:
this.checkOldProduct(oldProductsArray, listSelectedProducts)
in this function:
checkOldProduct(oldProductsArray, listSelectedProducts){
let arrayProductToDelete = []
// i have tried like this, but it doesn't work properly.
listSelectedProducts.filter(p1 => !oldProducts.some(p2 => { p1.productId === p2.productId, arrayProductToDelete.push(p2) }));
}
I hope I have expressed myself well, in any case I am ready to add clarifications. thank you
some quick consideration first:
If that's really all the data you have for the product list, you should probably just use a PUT method on your API and simply replace the whole list without having to compute the difference
If you still somehow need to make separate operations to update products list, I guess you should make POST requests to add new items, DELETE requests to delete items and PATCH requests to update single items (like same id but different quantities?)
From the point above: do you also have quantities for items?
Question Specific Answer
So based solely on your question I think easiest way is to find the list of items to delete something like this:
const removedItems = oldArray.filter((old) => !newArray.find((_new) => old.id === _new.id));
And request their deletion.
Full Diff Answer
If you want to compute a full diff of your chart items so you can make multiple update requests, you could do something like this:
function arrayDiff(oldArray, newArray) {
const addedAndUpdatedItems = newArray.reduce((diff, item, index, array) => {
const oldItem = oldArray.find((old) => old.id === item.id);
if(!oldItem) {
diff.added.push(item);
} else if(oldItem.quantity !== item.quantity) {
diff.updated.push(item);
}
return diff;
}, {
added: [],
updated: []
});
const removedItems = oldArray.filter((old) => !newArray.find((_new) => old.id === _new.id));
return {
...addedAndUpdatedItems,
removed: removedItems
}
}
const oldArray = [{ name: "prod1", id: 1, quantity: 1 }, { name: "prod3", id: 3, quantity: 4 }];
const newArray = [{ name: "prod1", id: 1, quantity: 3 }, { name: "prod2", id: 2, quantity: 3 }];
const diff = arrayDiff(oldArray, newArray);
console.log({ diff });
Output is
{
"added": [
{
"name": "prod2",
"id": 2,
"quantity": 3
}
],
"updated": [
{
"name": "prod1",
"id": 1,
"quantity": 3
}
],
"removed": [
{
"name": "prod3",
"id": 3,
"quantity": 4
}
]
}
If you want do find matching objects in both array using their productId,
Here is my solution
First get the productId list from one array then you can easily filter the another array using includes() method
let listSelectedProducts = [{name: "prod2", productId: 1}, {name: "prod2", productId: 2}, {name: "prod2", productId: 3}]
let oldProductsArray = [{name: "prod1", productId: 1}, {name: "prod1", productId: 2}]
let oldIds = oldProductsArray.map(d=>d.productId)
let arrayProductToDelete = listSelectedProducts.filter(d=>oldIds.includes(d.productId))
console.log(arrayProductToDelete)
I have an cart object. I want to change the qty of a product in cart for which i have created a changeQty method inside which I am using filter method to find the respective product and then I am updating the qty.
If I pass other than 0 it works properly. But if I pass 0 as quantity to the changeQty method the product get's removed from the cart.
According to my knowledge filter works based on the return value. But here I am assigning qty to product.qty. And also If I use map instead of filter I am geting 0 in place of that product. So my doubt is what does product.qty = qty returns ?
let cart = [{
id: 1,
title: "Product 1",
qty: 1
},
{
id: 2,
title: "Product 2",
qty: 1
},
]
const changeQty = (pid, qty) => {
cart = cart.filter(product => product.id === pid ? product.qty = qty : product)
console.log(cart);
}
changeQty(1, 0)
Use map instead of filter. You can use spread syntax and overwrite the value of qty for the product that matches the id.
let cart = [{
id: 1,
title: "Product 1",
qty: 1
},
{
id: 2,
title: "Product 2",
qty: 1
},
]
const changeQty = (pid, qty) => {
cart = cart.map(product => product.id === pid ? { ...product, qty } : product)
console.log(cart);
}
changeQty(1, 0)
if I pass 0 as quantity to the changeQty method the product get's removed from the cart.
That's because the assignment of 0 to product.qty evaluates to a 0 return value for the filter callback. That is a falsy value, indicating the current cart item should be filtered out. In fact, you don't want to filter at all. On the contrary, you don't want to change the length of the cart array.
If your goal is to mutate the cart structure, then possibly you are looking for find instead of filter:
const cart = [{id:1,title:"Product 1",qty:1},{id:2,title:"Product 2",qty:1}];
const changeQty = (pid, qty) => {
Object(cart.find(({id}) => id === pid)).qty = qty;
console.log(cart);
}
changeQty(1, 0);
NB: the call to Object is to deal silently with a product that is not in the cart.
You are modifying the source object which is very bad:
let cart = [{
id: 1,
title: "Product 1",
qty: 1
},
{
id: 2,
title: "Product 2",
qty: 1
},
]
const changeQty = (pid, qty) => {
cart.forEach(product => product.id === pid ? product.qty = qty : product)
console.log(cart);
}
changeQty(1, 0)
That's why you are getting unexpected results
Or you can do it with this one-liner:
let cart = [{id: 1,title: "Product 1",qty: 1},{id: 2,title: "Product 2",qty: 1}];
const changeQty = (pid, qty) =>( cart.forEach(p => p.id===pid && (p.qty=qty)), cart );
console.log(changeQty(1, 0));
As OP's intention was to change the input array cart the .forEach() methods presents itself as a suitable candidate.
I don't know how many cart items there might be with your p.id===pid. In my snippet they would all be changed by getting an updated qty property. Trincot provides an array.find() based solution that is probably more performant but will only affect the first cart item matching the specified pid.
I am working on a large product datasets and I need to filter through the products list based on the user's input value. My product dataset looks something like this:
const products = [
{id: 0, name: "Product1", brand: "Theraflu", itemCode: "THE110", price: 5.45},
{id: 1, name: "Product2", brand: "Benadryl", itemCode: "BEN121", price: 7.05},
{id: 2, name: "Product3", brand: "Listerine", itemCode: "LIS204", price: 4.55},
{id: 3, name: "Product4", brand: "Tylenol", itemCode: "TYL116", price: 6.10},
];
I was able to filter the product list based on the different fields available in each individual product object like this:
const keys = ["name", "brand", "itemCode"];
const getFilteredProducts = (filterText) => {
const newProducts = products.filter(product => keys.some(key => product[key].toLowerCase().includes(filterText.toLowerCase())));
return newProducts;
}
console.log(getFilteredProducts("Tylenol"));
This code actually works when I filter the product based on individual field. However, when I try to combine different fields like:
console.log(getFilteredProducts("product4 Tylenol"));
The returned value of this is an empty array. Is there a way to achieve this without altering the existing filtering functionality?
Seems like you'll need to search each word of the filterText on its own. Maybe something like this:
const keys = ["name", "brand", "itemCode"];
const getFilteredProducts = (filterText) => {
const filterWords = filterText.split(" ");
const newProducts = [];
for (const word of filterWords) {
newProducts.push(
products.filter(product => keys.some(key =>
product[key].toLowerCase().includes(word.toLowerCase())
))
);
}
return [].concat(...newProducts).filter((value, index, self) =>
self.findIndex((m) => m.id === value.id) === index
);
}
console.log(getFilteredProducts("Tylenol Product3"));
Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good
Hey guys this is my first project that i am making with Nodejs and js (ofcos).
So i reached the point where before displaying the products stored in user cart to the user i want to check if the Product still exists in the db or it has been removed and if it has been removed from db i do not want to show that product in user cart
So in mongoose method
userschema.methods.checkcart = function(product) {
let products=product
console.log(products) //stores all products that exist in db
};
gives me
[
{
_id: 5d31f00d6f2a111ebd6e98da,
title: 'Pepsi',
imageurl: 'https://target.scene7.com/is/image/Target/GUEST_26aa6df7-2fdf-4b4b-9f3b-d2ea31b5d685?wid=488&hei=488&fmt=pjpeg',
price: 12,
description: 'Hard !',
userid: 5d31e70115a10b1b5f7e6ed6,
__v: 0
},
{
_id: 5d31f0486f2a111ebd6e98db,
title: 'Burger!',
imageurl: 'https://www.seriouseats.com/recipes/images/2015/07/20150702-sous-vide-hamburger-anova-primary-1500x1125.jpg',
price: 123,
description: 'Tasty.',
userid: 5d31e70115a10b1b5f7e6ed6,
__v: 0
},
]
now i want to look for the products that user has on the cart so
let cartproducts=this.cart.items
console.log(cartproducts) //gives
[
{
"_id": "5d322eb241f5e836068485db",
"productid": "5d31f00d6f2a111ebd6e98da",
"quantity": 2
},
{
"_id": "5d322ec041f5e836068485dc",
"productid": "5d31f0486f2a111ebd6e98db",
"quantity": 1
},
{
"_id": "5d322ec741f5e836068485dd",
"productid": "5d31f0636f2a111ebd6e98dc",
"quantity": 1
}
]
Now as u can see that product with id 5d31f0636f2a111ebd6e98dc has been removed from db so i wanna filter them and return only those products which are in cart and db.
P.s - Tried hell lot with map and filter method but everytime created a mess !
:(
const products = [
{
_id: '5d31f00d6f2a111ebd6e98da',
title: 'Pepsi',
imageurl: 'https://target.scene7.com/is/image/Target/GUEST_26aa6df7-2fdf-4b4b-9f3b-d2ea31b5d685?wid=488&hei=488&fmt=pjpeg',
price: 12,
description: 'Hard !',
userid: '5d31e70115a10b1b5f7e6ed6',
__v: 0
},
{
_id: '5d31f0486f2a111ebd6e98db',
title: 'Burger!',
imageurl: 'https://www.seriouseats.com/recipes/images/2015/07/20150702-sous-vide-hamburger-anova-primary-1500x1125.jpg',
price: 123,
description: 'Tasty.',
userid: '5d31e70115a10b1b5f7e6ed6',
__v: 0
},
];
const cartProducts = [
{
"_id": "5d322eb241f5e836068485db",
"productid": "5d31f00d6f2a111ebd6e98da",
"quantity": 2
},
{
"_id": "5d322ec041f5e836068485dc",
"productid": "5d31f0486f2a111ebd6e98db",
"quantity": 1
},
{
"_id": "5d322ec741f5e836068485dd",
"productid": "5d31f0636f2a111ebd6e98dc",
"quantity": 1
}
];
const productIds = products.map(prod => prod._id);
const filteredCartProducts = cartProducts
.filter(prod => productIds.includes(prod.productid));
console.log(filteredCartProducts);
With products beign an array, you need to go over all elements to find out if it contains it. That doesn't perform good with bigger datasets.
Just the filtered cart items:
cartproducts.filter(item => products.some(product => product._id === item.productid))
You probably want both together, the cart items and the products. That could be achieved like so:
cartproducts
.map(item => ({
item,
product: products.find(product => product._id === item.productid),
}))
.filter(both => both.product)
(filter after the map, to not search the products array twice.)
By using find and some instead of includes as in Rocky Sims answer, you don't need an extra productIds array.
Normally in a database you would have an index on the _id, so that it is fast to find.
Instead of an array, your products could be an object where the keys are the _id of the products:
const productsById = {
5d31f00d6f2a111ebd6e98da: {
_id: '5d31f00d6f2a111ebd6e98da',
title: 'Pepsi',
// [...]
},
5d31f0486f2a111ebd6e98db: {
_id: '5d31f0486f2a111ebd6e98db',
title: 'Burger!',
// [...]
},
}
The products array could be converted to an object like this:
const productsById = products.reduce(
(map, product) => {
map[product._id] = product
return map
},
{}
)
Then it would be very easy and performant to filter:
cartproducts.filter(item => item.productid in productsById)
Both:
cartproducts
.filter(item => item.productid in productsById)
.map(item => ({
item,
product: productsById[item.productid],
}))