I'm trying to implement a shopping cart by using contexts. The context works fine, but I need to create a method that removes one product each click. To do that I have to use useState() function. However, it doesn't even change the data in the state.
Here is my code for the function. Let me know if something isn't clear
For more info. productsToPurchase is an array that have all the purchased products which looks like that:
[{itemId: "ps-5", qty:2},{itemId: "iphone-xr", qty:4},{itemId:"ps-4", qty:7}]
export const CartContext = createContext()
class CartContextProvider extends Component {
state = {
productsToPurchase: [],
totalPrice:[]
}
addProduct = (itemId, prices) => {
this.setState(oldState => {
const objWithIdExist = oldState.productsToPurchase.find((o) => o.itemId === itemId);
return {
productsToPurchase: !objWithIdExist
? [...oldState.productsToPurchase, { itemId, qty: 1, price: prices }]
: oldState.productsToPurchase.map((o) =>
o.itemId !== itemId ? o : { ...o, qty: o.qty + 1 }
)
}
})
this.setState(oldState=>{
return{
totalPrice: getTotalAmount(oldState.productsToPurchase)
}
})
}
decreaseProduct = (itemId) =>{
this.setState(oldState=>{
// catch the item by mapping
return oldState.productsToPurchase.map(product=>{
if (product.itemId === itemId){
if (product.qty === 1){
//remove the product from the list
console.log("qty is 1!")
}
else {
console.log("Quantity is more than 1")
return {...product, qty: product.qty - 1}
}
}
})
})
}
render() {
return (
<CartContext.Provider value={{...this.state, addProduct: this.addProduct, decreaseProduct: this.decreaseProduct}}>
{this.props.children}
</CartContext.Provider>
)
}
}
function getTotalAmount(productsToPurchase) {
const totalPrice = []
productsToPurchase.map((product, productIndex) =>{
product.price.map((price, priceIndex)=>{
if (productIndex === 0){
totalPrice.push({currency: price.currency, amount: price.amount * product.qty})
}
else {
totalPrice.map(total=>{
if (total.currency === price.currency){
total.amount = total.amount + (price.amount * product.qty)
}
})
}
})
})
return totalPrice
}
export default CartContextProvider;
If you want to subtract 1 from the selected product quantity and remove it if it hits 0, try this
// Make sure you return the new state object in its entirety
this.setState(({ productsToPurchase, ...oldState }) => ({
...oldState,
productsToPurchase: productsToPurchase.reduce((arr, product) => {
// is this the selected product?
if (product.itemId === itemId) {
// product qty > 1, include it with decrement
if (product.qty > 1) {
return [...arr, {
...product,
qty: product.qty - 1
}]
}
return arr // otherwise exclude this product
}
// otherwise, just include this product
return [...arr, product]
}, [])
}))
Related
So basically I am making a shopping cart and I want to add a functionality if an item is already in the cart then increase it's quantity by 1. If you add same item and they have different sizes then show them separetely. I managed to deal with increasing the quantity in my reducer's logic but when I add another block condition it doesn't work. Here is the code:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
bagData: [],
};
export const bagDataSlice = createSlice({
name: "bagDataProducts",
initialState,
reducers: {
sendDataToCardComponent: (state, action) => {
let { id, size } = action.payload;
const findItemById = state.bagData.find(
(item) => item.id === id && item.size === size
);
if (findItemById) {
state.bagData.filter((item, i) => (state.bagData[i].quantity += 1));
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
},
increaseItemQuantity: (state, { payload }) => {
state.bagData.filter((item, i) =>
item.id === payload ? (state.bagData[i].quantity += 1) : item
);
},
decreaseItemQuantity: (state, { payload }) => {
state.bagData.filter((item, i) =>
item.id === payload && item.quantity > 1
? (state.bagData[i].quantity -= 1)
: item
);
},
removeItem: (state, { payload }) => {
state.bagData = state.bagData.filter((item) => item.id !== payload);
},
},
});
when I add the condition of
const findItemById = state.bagData.find(
(item) => item.id === id && item.size === size
);
it only increments the item without checking it's size, only checks it's id even though there are 2 conditions for that function. Could you please explain that to me?
state.bagData.filter((item, i) => (state.bagData[i].quantity += 1));
For your first case, this is updating every item's quantity if you found a matching item by id and size. Since you've already found the item and stored it in findItemById, you should be able to use the following.
Caveat, Immer only supports mutating array elements by index so use findIndex() instead of find().
const itemIndex = state.bagData.findIndex(
(item) => item.id === id && item.size === size
);
if (itemIndex !== -1) {
state.bagData[itemIndex].quantity++;
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
Here's a quick demo showing that this works
const initialState = {
bagData: [{
id: 1,
quantity: 1,
size: "S"
}]
};
const sendDataToCardComponent = (action) =>
immer.produce(initialState, (state) => {
let { id, size } = action.payload;
const itemIndex = state.bagData.findIndex(
(item) => item.id === id && item.size === size
);
if (itemIndex !== -1) {
state.bagData[itemIndex].quantity++;
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
});
console.log(
"increment existing",
sendDataToCardComponent({ payload: { id: 1, size: "S" } })
);
console.log(
"add new",
sendDataToCardComponent({ payload: { id: 1, size: "M" } })
);
.as-console-wrapper { max-height: 100% !important; }
<script src="https://cdn.jsdelivr.net/npm/immer"></script>
As mentioned in the comments, you're misusing Array.prototype.filter() which should only be used to return a new array with items filtered in or out based on a predicate. Your code can be cleaned up somewhat
increaseItemQuantity: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData[found].quantity++;
}
},
decreaseItemQuantity: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData[found].quantity--;
}
},
Your last reducer is using filter() correctly but Immer also supports splice()
removeItem: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData.splice(found, 1);
}
},
When I add the product to the cart and then I add another one, then if I try to add the first one again it doesn't increase only the quantity but it creates a new object for this product; how can I fix it
switch (action.type) {
case actionTypes.ADD_TO_CART:
const product = state.products.find((p) => p.id === action.payload.id);
const inCart = state.cart.find((item) =>
item?.product.id === action.payload.id ? true : false
);
if (inCart) {
let check = false;
state.cart.map((item, key) => {
if (
item.product.id === action.payload.id &&
item.setVar === action.payload.setVar
) {
state.cart[key].quantity++;
check = true;
}
});
if (!check) {
const newItem = {
product: product,
setVar: action.payload.setVar,
quantity: 1,
};
state.cart.push(newItem);
}
} else {
const newItem = {
product: product,
setVar: action.payload.setVar,
quantity: 1,
};
state.cart.push(newItem);
}
return {
...state,
};
}
item.product.id === action.payload.id && JSON.stringify(item.setVar) === JSON.stringify(action.payload.setVar)
I am doing a React JS Cart and I am having problems when I try to delete an Item from the there. It has already a function that adds the items and also another for the total quantity and the total price.
This is the ContextProvider:
import { useState } from "react";
import { CartContext } from "./CartContext";
export const CartProvider = ({ children }) => {
const [list, setList] = useState([]);
const addCart = (varietalCount) => {
if (list.find((item) => item.id === varietalCount.id)) {
const newVarietal = list.map((varietal) => {
if (varietal.id === varietalCount.id) {
return { ...varietal, count: varietalCount.count + varietal.count };
}
return varietal;
});
setList(newVarietal);
} else {
setList((state) => {
return [...state, varietalCount];
});
}
};
console.log("list", list);
// const deleteProd = (varietalCount) => {
// if (list.find((item) => item.id === varietalCount.id)) {
// const deleteVarietal = list.map((varietal) => {
// if (varietal.id === varietalCount.id) {
// return { ...varietal, count: null };
// }
// return varietal;
// });
// setList(deleteVarietal);
// } else {
// setList((state) => {
// return [...state, varietalCount];
// });
// }
// };
const totalPrice = () => {
return list.reduce((prev, next) => (prev + (next.count * next.price)), 0)
};
const totalQuantity = () => {
return list.reduce((prev, next) => (prev + (next.count)), 0)
};
return(
<>
<CartContext.Provider value={{ list, addCart, totalPrice, totalQuantity }}>
{children}
</CartContext.Provider>
</>);
};
If it is necessary I can add to the post the Cart.js or the ItemDetail.js. I hope someone can help me. Cheers
I think you can just use filter given that your state has value of an array. Something like:
const deleteProd = (varietalCount) => {
const newItems = list.filter((item) => item.id !== varietalCount.id)
setList(newItems);
};
You can check more array functions from here https://www.w3schools.com/jsref/jsref_obj_array.asp
I know that redux trigger react component to re-render once state of that component get changed but this doesn't happen in my situation.
Action
const addToCart = (product, cartProducts) => {
let newCartProducts = [...cartProducts, product];
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeated = acc.find(p => p.id === cur.id);
if(repeated) repeated.quantity++;
else acc.push(cur);
return acc;
}, []);
return {
type: ADD_TO_CART,
payload: newCartProducts
}
}
Reducer:
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return action.payload;
default:
return state;
}
}
The reducer returns a new state every time the action dispatched from the component but i need to close the cart and open it again to get the effect, redux does not update the product quantity simultaneously??
You are modifying the existing elements in the state.
Use
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeatedIndex = acc.findIndex(p => p.id === cur.id);
const repeated = acc[repeatedIndex];
if (repeated) {
acc[repeatedIndex] = {
...repeated,
quantity: repeated.quantity + 1
};
} else acc.push(cur);
return acc;
}, []);
You array is recreated each time, but the objects inside it are not. So when you modify their internals you need to notify that the specific object has changed.
Refactor logic to the reducer and set the quantity here:
const addToCart = product => {
return {
type: ADD_TO_CART,
payload: product,
};
};
//I assume state is an array of products on your cart
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
const { id } = action.payload;
return state.map(p => p.id).includes(id)
? //product is already on card add quanity
state.map(p =>
p.id === id
? { ...p, quantity: p.quantity + 1 }
: p
)
: state.concat({ ...action.payload, quantity: 1 }); // add product
default:
return state;
}
};
I am trying to add items to a cart without redux. I works but I am dont think I did it properly because I get a weird quantity value.
How it works so far.
You click an item button it passes in props to the handleAddCartItem(). The props are addedItem which is the name of the item and addedItemPrice.
The default state
state = {
term: "",
cart: [
{
item: "",
price: 0,
quantity: 0
}
]};
how handler works.
handleAddCartItem = (addedItem, addedItemPrice) => {
// check if already in cart
let index = this.state.cart.findIndex(el => el.item === addedItem);
if (index == -1) {
console.log("item new to cart");
this.setState(prevState => ({
cart: [
...prevState.cart,
{
item: addedItem,
price: addedItemPrice,
quantity: 1
}
]
}));
} else {
this.setState(prevState => ({
quantity: (prevState.cart[index].quantity += 1)
}));
} };
The weird extra quantity state screenshot.
I am new to react so the code is probably gross sorry.
My logic in the handler
- checks in item name is already inside the cart array.
-if it newly added item then new object is added to array.
-if item is already in array then I use the index of the item currently inside the array and only update its quantity.
I don't understand why I have an extra quantity state added =/
Any advice appreciated thank you.
I belive code speaks for itself but if you're confused about something, ask me.
handleAddCartItem = (addedItem, addedItemPrice) => {
// check if already in cart
let index = this.state.cart.findIndex(el => el.item === addedItem);
if (index == -1) {
console.log("item new to cart");
this.setState(prevState => ({
cart: [
...prevState.cart,
{
item: addedItem,
price: addedItemPrice,
quantity: 1
}
]
}));
} else {
// THIS IS THE PROBLEM
this.setState(prevState => ({
quantity: (prevState.cart[index].quantity += 1)
}));
// HOW IT SHOULD BE
this.setState(prevState => {
let newCart = [...prevState.cart];
newCart[index].quantity += 1;
return {cart: newCart};
});
}
}
quantity in the necessary item object needs to be incremented. But instead another variable quantity was created at the root. Try updating the else block to this :
else {
this.setState(prevState => ({
cart: prevState.cart.map((item, itemIndex) => {
if(itemIndex === index) {
return {
...item,
quantity: item.quantity + 1
}
}
return item
})
}));
}