Getting lost in a deeply nested setState function.
What I am trying to do is to add an object to an array within an object within my components state. I have succeeded in doing so but I am trying to make it so if that object already exists, the function will update the quantity. I'm not sure what I'm doing wrong.
state
this.state = {
products : [
{
productId: 1,
productImage: 'tee1',
productName: 'The Road Is Home Tee',
productPrice: 25
},
{
productId: 2,
productImage: 'shorts1',
productName: 'Striped Swim Shorts',
productPrice: 50
},
{
productId: 3,
productImage: 'tee2',
productName: 'Gray Long Sleeve',
productPrice: 100
},
{
productId: 4,
productImage: 'hat1',
productName: 'American Snapback',
productPrice: 25
},
{
productId: 5,
productImage: 'shorts2',
productName: 'American Shorts',
productPrice: 50
},
{
productId: 6,
productImage: 'hat2',
productName: 'Flex Fit Hat',
productPrice: 100
}
],
cartData : {
items: [],
total: 0
}
}
addToCart()
addToCart = (productId, size, quantity) => {
for( let i=0; i < this.state.cartData.items.length; i++ ) {
if (productId === this.state.cartData.items[i].productData.productId) {
this.setState(prevState => {
const items = prevState.cartData.items.map(item => {
if(i === (item.productId -1)) {
return item.productQuantity + quantity;
}
})
return {
items,
}
})
} else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
EDIT ***********
NEW CODE IS MORE ORGANIZED AND I HAVE IMPLEMENTED AN IF STATEMENT IN THE BEGINNING THAT CHECKS IF CART IS EMPTY BEFORE I LOOP OVER THE ITEMS. ALSO FIXED A COUPLE SMALL THINGS AND ADDED SOME COMMENTS BUT STILL NOT GETTING THE RESULTS I WOULD LIKE
addToCart = (productId, size, quantity) => {
// IF CART IS EMPTY (CANT USE FOR LOOP)
if ( this.state.cartData.items.length === 0 ) {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
// IF CART IS NOT EMPTY
else {
// LOOP THROUGH EACH ITEM
for( let i=0; i < this.state.cartData.items.length; i++ ) {
// IF PRODUCT EXISTS
if (productId === this.state.cartData.items[i].productData.productId) {
// SET STATE (RETURNS A FUNCTION)
this.setState(prevState => {
// MAP THROUGH EACH ITEM
const items = prevState.cartData.items.map(item => {
// IF THE PRODUCT IDS MATCH
if(i === (item.productData.productId -1)) {
// RETURN AN OBJECT WITH SAME ITEM PROPERTIES
// ADJUST QUANTITY
return {
...item,
productQuantity: item.productQuantity + quantity,
}
}
// RETURN UNAFFECTED ITEMS AS WELL
else {
return item
}
})
// RETURN OUR ITEMS TO SET STATE
return {
items,
}
})
}
// IF PRODUCT DOES NOT EXIST
else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
}
AS OF NOW MY APPLICATION WILL ADD THE FIRST PRODUCT CORRECTLY. THEN ANYTHING AFTER THAT I START GETTING PROBLEMS. WHEN I TRY TO ADD ANOTHER PRODUCT WITH THE SAME PRODUCT ID IT WILL NOT JUST INCREMENT THE QUANTITY IT CREATES A WHOLE OTHER OBJECT.
sooo lost here please help!!!
p.s.
getting errors that i am not getting with localhost inside of sandbox so not really an option here
I didn't try running the code (will do if you provide a codesandbox or something), but that should help.
addToCart = (productId, size, quantity) => {
for( let i=0; i < this.state.cartData.items.length; i++ ) {
if (productId === this.state.cartData.items[i].productData.productId) {
this.setState(prevState => {
const items = prevState.cartData.items.map(item => {
if(i === (item.productId -1)) {
// returns a copy of the known item with updated quantity
return {
...item,
productQuantity: item.productQuantity + quantity,
};
} else {
return item // you need to return items that are not modified too
}
})
return {
items,
}
})
} else {
this.setState(prevState => ({
cartData: {
...prevState.cartData,
items: [...prevState.cartData.items, { productData: this.state.products[productId - 1], productSize: size, productQuantity: quantity }]
}
}))
}
}
}
It works. I hope it helps you. (some changes for a more organized code):
const products = [
{
productId: 1,
productImage: 'tee1',
productName: 'The Road Is Home Tee',
productPrice: 25
},
{
productId: 2,
productImage: 'shorts1',
productName: 'Striped Swim Shorts',
productPrice: 50
},
{
productId: 3,
productImage: 'tee2',
productName: 'Gray Long Sleeve',
productPrice: 100
},
{
productId: 4,
productImage: 'hat1',
productName: 'American Snapback',
productPrice: 25
},
{
productId: 5,
productImage: 'shorts2',
productName: 'American Shorts',
productPrice: 50
},
{
productId: 6,
productImage: 'hat2',
productName: 'Flex Fit Hat',
productPrice: 100
}
]
const [cart, setCart] = useState({
items: [],
total: 0
})
const isProductExist= productId =>
cart.items.find(product => product.productId == productId)
addToCart = (productId, size, quantity) => {
if (isProductExist(productId)) {
setCart(prevState => ({
...prevState,
items: cart.items.map(item => {
if (item.productId == productId) item.productQuantity += quantity
item.productQuantity
return item
})
}))
} else {
setCart(prevState => ({
...prevState,
items: [
...prevState.items,
{
productId: products[productId - 1].productId,
productSize: size,
productQuantity: quantity
}
]
}))
}
}
Related
The state for cart is following;
State> Cart> Products
{
'0': {
_id: '63c6e3d5f73ff2b0604b4e9c',
name: 'Oriental Rubber Table',
price: 521,
category: 'Pizza',
image: 'https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
**amount: 1**
}
}
And I have this function to increase amount of the product. But It increases all the products in the basket not only the chosen item.
incrementAmount: (state, action) => {
return {products: state.products.map(product => {
if(product.id === action.payload){
return {...product, amount: product.amount + 1}
}else{
return product
}
})}
},
Given an array of objects, containing products. A single object contains a single product offer.
Products can have a identical productId, while offerId are unique for each product.
Process the data to get the cheapest priced item for each offer
const data = [
{ productId: 'dhdiwu', offerId: 'd3en', price: '$12.20' },
{ productId: 'dhdiwu', offerId: 's2dr', price: '$8.45' },
{ productId: 'dhdiwu', offerId: 'hy38', price: '$21.21' },
{ productId: 'dowksm', offerId: 'ie8u', price: '$1.77' },
{ productId: 'dowksm', offerId: 'djs3', price: '$24.21' },
{ productId: 'dowksm', offerId: 'pies', price: '$92.36' },
{ productId: 'bdbhsu', offerId: '9wid', price: '$100.98' }
]
const dataArray = data.reduce((prev, t, index, arr) => {
if (typeof prev[t.productId] === 'undefined') {
prev[t.productId] = [];
}
prev[t.productId].push(t);
return prev;
}, {});
let same_id = []
let cheapest = []
Object.keys(dataArray).forEach(i => {
same_id.push(dataArray[i]);
});
console.log(same_id)
//output for now
/*[
[
{ productId: 'dhdiwu', offerId: 'd3en', price: '$12.20' },
{ productId: 'dhdiwu', offerId: 's2dr', price: '$8.45' },
{ productId: 'dhdiwu', offerId: 'hy38', price: '$21.21' }
],
[
{ productId: 'dowksm', offerId: 'ie8u', price: '$1.77' },
{ productId: 'dowksm', offerId: 'djs3', price: '$24.21' },
{ productId: 'dowksm', offerId: 'pies', price: '$92.36' }
],
[ { productId: 'bdbhsu', offerId: '9wid', price: '$100.98' } ]
]*/
I would first start by grouping the products by productId, something like what is suggested here should work: Most efficient method to groupby on an array of objects
function groupByKey(array, key) {
const groupedObject = {}
for (const item of array) {
const value = item[key]
if (groupedObject[value] === undefined) {
groupedObject[value] = []
}
groupedObject[value].push(item)
}
return groupedObject
}
groupByKey(data, 'productId')
Now you have an object with three properties, the unique productID's with each product inside it. Then loop through each one, find the lowest price.
const grouped = groupByKey(data, 'productId');
const lowest = {};
for (const group of Object.keys(grouped)) {
if (!lowest[group]) {
lowest[group] = '';
}
for (const product of grouped[group]) {
if (lowest[group] === '') {
lowest[group] = product.price
}
if (product.price < lowest[group]) {
lowest[group] = product.price;
}
}
}
console.log(lowest);
// {dhdiwu: '$12.20', dowksm: '$1.77', bdbhsu: '$100.98'}
It's a little scrappy, and I'm sure there are some cool one-liners you could build, but that's the general idea.
If I understand correctly, you're looking to find the lowest priced offer for each productId.
You can go with this:
const data = [
{ productId: 'dhdiwu', offerId: 'd3en', price: '$12.20' },
{ productId: 'dhdiwu', offerId: 's2dr', price: '$8.45' },
{ productId: 'dhdiwu', offerId: 'hy38', price: '$21.21' },
{ productId: 'dowksm', offerId: 'ie8u', price: '$1.77' },
{ productId: 'dowksm', offerId: 'djs3', price: '$24.21' },
{ productId: 'dowksm', offerId: 'pies', price: '$92.36' },
{ productId: 'bdbhsu', offerId: '9wid', price: '$100.98' }
]
// group by productId and find the lowest price
const result = data.reduce((acc, { productId, offerId, price }) => {
const current = acc[productId]
if (!current || current.price > price) {
acc[productId] = { offerId, price }
}
return acc
}, {})
console.log(result)
output:
node .\scratch.js
{
dhdiwu: { offerId: 'd3en', price: '$12.20' },
dowksm: { offerId: 'ie8u', price: '$1.77' },
bdbhsu: { offerId: '9wid', price: '$100.98' }
}
I have this complex models which i am trying to map.
I have an array of Carts containing id, name and etc
I have a dictionary where key is a type and its value is the different products.
I have added an example of what the result should looks like at the bottom of page.
My attempt but got stuck on how to filter
carts.forEach(cart => {
const productList = cart.products; //list of products
Object.entries(content.data).forEach(([key, values]) => {
const sizeList = values.map(x => x.sizeList); //list of size
// i am stuck here
});
});
const getCartsWithProductNames = (carts = [], content = {}) => {
// create a Map with productId-sizeId as key, and size name as value
const productSizeNameMap =
Object.values(content)
.flat()
.reduce((map, { productId, sizeList = [] }) => {
sizeList.forEach(({ sizeId, name }) =>
map.set(`${productId}-${sizeId}`, name)
);
return map;
}, new Map);
// return carts list with product items having names from productSizeNameMap
return carts.map(({ cartId, cartName, products = [] }) => ({
id: cartId,
name: cartName,
productItems: products.map(({ id, size, quantity, isVisible }) => ({
id, quantity, isVisible, name: productSizeNameMap.get(`${id}-${size}`)
}))
}));
}
const
carts = [
{
cartId: 500,
cartName: "Some name",
products: [ { id: 1, size: 10, quantity: 1, isVisible: true }, { id: 2, size: 13, quantity: 10, isVisible: true } ]
}
],
content = {
"Drinks": [
{ productId: 1, sizeList: [ { sizeId: 10, name: "100ml beer" }, { sizeId: 9, name: "200ml beer" } ]
}
],
"Food": [
{
productId: 2,
sizeList: [ { sizeId: 12, name: "6 wings" }, { sizeId: 13, name: "12 wings" } ]
}
]
};
console.log( getCartsWithProductNames(carts, content) );
const products = [{
id: 5,
productName: "Logitech Mouse",
unitprice: 35
},
{
id: 6,
productName: "Logitech Keyboard",
unitprice: 40
}
];
const cart = [{
id: 101,
userId: 3,
productId: 5,
quantity: 2
}];
totals = cart.reduce((r, {
productId: id,
quantity
}) =>
(r[id] = (r[id] || 0) + quantity, r), {}),
result = products.map(({
id,
productName,
unitprice
}) => ({
productName,
unitprice,
quantity: totals[id]
}));
console.log(result)
Currently, it prints two rows instead of one row. Second row should not be printed because there is only one row in cart. How do I the so-called inner join which return only one record which is for cart id 101?
If you want retain the cart with the "join", you can map the found product (without its id) and include the quantity.
const products = [{
id: 5,
productName: "Logitech Mouse",
unitprice: 35
}, {
id: 6,
productName: "Logitech Keyboard",
unitprice: 40
}];
const cart = [{
id: 101,
userId: 3,
productId: 5,
quantity: 2
}];
let joined = cart.map(item => {
let { id, ...rest} = products.find(p => p.id === item.productId);
return { ...rest, 'quantity' : item.quantity };
});
console.log(joined);
.as-console-wrapper { top: 0; max-height: 100% !important; }
If you want the total price of all items in the cart you will need to:
Reduce the cart by the items within it
Locate the product by its id
Add to the total, the unit price of the product times the quantity in the cart
const products = [{
id: 5,
productName: "Logitech Mouse",
unitprice: 35
}, {
id: 6,
productName: "Logitech Keyboard",
unitprice: 40
}];
const cart = [{
id: 101,
userId: 3,
productId: 5,
quantity: 2
}];
let total = cart.reduce((subtotal, item) => {
let product = products.find(p => p.id === item.productId);
return subtotal + product.unitprice * item.quantity;
}, 0);
console.log(total);
Short version:
let t = cart.reduce((s, i) => s + products.find(p => p.id === i.productId).unitprice * i.quantity, 0);
I recommend to use map function like this
const products = [{
id: 5,
productName: "Logitech Mouse",
unitprice: 35
},
{
id: 6,
productName: "Logitech Keyboard",
unitprice: 40
}
];
const cart = [{
id: 101,
userId: 3,
productId: 5,
quantity: 2
}];
result = cart.map(item => {
const product = products.find(product => item.productId === product.id);
return { id: item.id, quantity: item.quantity, productName: product.productName, unitprice: product.unitprice }
});
console.log(result);
While Mr. Polywhirl's answer is a cleaner and better solution, here is a simple edit to your code that solves your problem. The reason why your code returns two is because of the map. Replace it with filter and the condition on the presence of id to solve the issue.
const products = [{
id: 5,
productName: "Logitech Mouse",
unitprice: 35
},
{
id: 6,
productName: "Logitech Keyboard",
unitprice: 40
}
];
const cart = [{
id: 101,
userId: 3,
productId: 5,
quantity: 2
}];
totals = cart.reduce((r, {
productId: id,
quantity
}) =>
(r[id] = (r[id] || 0) + quantity, r), {}),
result = products.filter(({
id,
productName,
unitprice
}) => {
if (totals[id]) return ({
productName,
unitprice,
quantity: totals[id]
})
});
console.log(result)
I'm building a simple shopping cart for a site and have been working on the add to cartaction. While I have ti working I feel there is probably a simple more elegant way of doing it.
This is the starting state:
start_state = {
inventory: [
{sku: "product_1", price: 600, name: "Product 1"},
{sku: "product_2", price: 800, name: "Product 2"}
],
cart: []
}
And this is the desired end state:
start_state = {
inventory: [
{sku: "product_1", price: 600, name: "Product 1"},
{sku: "product_2", price: 800, name: "Product 2"}
],
cart: [
{sku: "product_1", quantity: 2},
{sku: "product_2", quantity: 1}
]
}
And this is the function Im triggering to take it from the initial state to new final_state, the sku argument is the item from the state that is passed in when the action is called:
addToCart: function (sku) {
let currentCart = this.state.cart
let itemInCart = _.findIndex(currentCart, ['sku', sku])
let newItem = { sku: sku }
if (itemInCart !== -1) {
let newQuantity = currentCart[itemInCart].quantity
newItem.quantity = newQuantity + 1
} else {
newItem.quantity = 1
}
let filteredCart = _.filter(currentCart, (item) => { return item.sku !== sku })
let newCart = _.concat(filteredCart, newItem)
this.setState({cart: newCart})
},
Since you are using ES6, you can use some of its new features like findIndex and Object.assign to achieve what you want.
addToCart: function(product) {
let index = this.state.cart.findIndex((x) => x.sku === product.sku);
if(index === -1) {
let newProduct = {sku: product.sku, quantity:1}
this.setState({cart : this.state.cart.concat([newProduct])})
}
else {
let newCart = Object.assign([], this.state.cart);
newCart[index].quantity = newCart[index].quantity+1;
this.setState({cart: newCart});
}
}
full working example
I think this way is better:
function getCardWithIncItem(currentCart, itemInCart) {
return [
...currentCart.slice(0, itemInCart),
Object.assign({}, currentCart[itemInCart], {
quantity: currentCart[itemInCart].quantity + 1,
}),
...currentCart.slice(itemInCart + 1),
];
}
function getCardWithNewItem(currentCart, sku) {
return [
...currentCart, {
sku: sku,
quantity: 1,
}
];
}
const currentCart = this.state.cart;
const itemInCart = _.findIndex(currentCart, ['sku', sku]);
const newCart = (itemInCart !== -1)
? getCardWithIncItem(currentCart, itemInCart)
: getCardWithIncItem(currentCart, sku);
this.setState({
cart: newCart,
})