I am using react context to add item to the cart and I want to prevent duplicate items being added in the cart, I have tried tweaking the code, but it seems like something is missed. Below is the code sample:
const cartReducer = (cartState, action) => {
switch (action.type) {
case "add-cart": {
const item = action.payload;
const existItem = cartState.cart.find((x) => x.id === item.id);
if (existItem) {
return {
cart: [...cartState.cart, action.payload],
};
} else
return {
...cartState,
cart: [...cartState.cart, action.payload],
};
}
}
};
export const CartProvider = (props) => {
const [cartState, dispatch] = useReducer(cartReducer, {
cart: [],
});
function addToCart(val) {
dispatch({
type: "add-cart",
payload: val,
});
}
Why are you insert your item even if item already exists in the cart ?
You don't have to put your action.payload in the cart if already exists
if (existItem) {
return { ...cartState };
} else {
return {
...cartState,
cart: [...cartState.cart, action.payload],
};
}
Related
I have 4 products which I would like to do toggle handler for. User can choose only one of them. Every product will have pernament ID at the shopping cart.
If I click for one of the product, 3 of them should not be in the current cart - later if I change mind and click on another product, current one should be removed from the cart. I can recognize them by ID. I have a problem to make toggle handler. Can someone help me how funcion should looks for something like that? As later, depend on the choosen product, I wanna show diffrent price of other product and if change the basic ones, everything will change in the cart.
That's my functions in cart file for update items, add item and remove item. It's for all items in store, not only these 4 basic products. I would like to rewrite addToCartHandler for checking at the same time if there are already other ID prodcuts and if yes, remove them.
const updateCartHandler = async (item) => {
const existItem = cartItems.find((x) => x._id === product._id);
const quantity = existItem ? existItem.quantity - 1 : 1;
// const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert("Sorry. Product is out of stock");
return;
}
ctxDispatch({
type: "CART_ADD_ITEM",
payload: { ...item, quantity },
});
};
const addToCartHandler = async (item) => {
const existItem = cartItems.find((x) => x._id === product._id);
const quantity = existItem ? existItem.quantity + 1 : 1;
// const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert("Sorry. Product is out of stock");
return;
}
ctxDispatch({
type: "CART_ADD_ITEM",
payload: { ...item, quantity },
});
};
const removeItemHandler = (item) => {
ctxDispatch({ type: "CART_REMOVE_ITEM", payload: item });
};
Store:
import { createContext, useReducer } from "react";
export const Store = createContext();
const initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
function reducer(state, action) {
switch (action.type) {
case "CART_ADD_ITEM":
// add to cart
const newItem = action.payload;
const existItem = state.cart.cartItems.find(
(item) => item._id === newItem._id
);
const cartItems = existItem
? state.cart.cartItems.map((item) =>
item._id === existItem._id ? newItem : item
)
: [...state.cart.cartItems, newItem];
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
case "CART_REMOVE_ITEM": {
const cartItems = state.cart.cartItems.filter(
(item) => item._id !== action.payload._id
);
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
}
default:
return state;
}
}
export function StoreProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Store.Provider value={value}>{props.children}</Store.Provider>;
}
thank you for helping
hi .
I am planning to create a shopping cart system for my site. I use React and Redux toolkit. But when I want to dispatch my states, I encounter this error. Thank you for helping me.
cartSlide (Cart Reducer) :
import {createSlice} from "#reduxjs/toolkit";
const initialState = {
selectedItems: [],
itemsCounter: 0,
total: 0,
checkout: false
}
const sumItems = items => {
const itemsCounter = items.reduce((total , product) => total + product.quantity, 0)
const totalPrice = items.reduce((total , product) => total + product.price * product.quantity,0).toFixed(2)
return {
itemsCounter,
totalPrice
}
}
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
AddItem:(state,action)=>{
if (!state.selectedItems.find(item => item.id === action.payload.id)) {
state.selectedItems.push({
...action.payload,
quantity: 1
})
}
return {
...state,
selectedItems: [...state.selectedItems],
...sumItems(state.selectedItems),
checkout: false
}
},
RemoveItem: (state, action) => {
const newSelectedItems = state.selectedItems.filter(item => item.id !== action.payload.id);
return {
...state,
selectedItems: [...newSelectedItems],
...sumItems(newSelectedItems)
}
},
Increase: (state, action) => {
const indexI = state.selectedItems.findIndex(item => item.id === action.payload.id);
state.selectedItems[indexI].quantity++;
return {
...state,
...sumItems(state.selectedItems)
}
},
Decrease: (state, action) => {
const indexD = state.selectedItems.findIndex(item => item.id === action.payload.id);
state.selectedItems[indexD].quantity--;
return {
...state,
...sumItems(state.selectedItems)
}
},
Checkout: () => {
return {
selectedItems: [],
itemsCounter: 0,
total: 0,
checkout: true
}
},
Clear: () => {
return {
selectedItems: [],
itemsCounter: 0,
total: 0,
checkout: false
}
}
}
})
export const {AddItem,RemoveItem,Increase,Decrease,Checkout,Clear} = cartSlice.actions
export default cartSlice.reducer
The error is for the AddItem action, and when I delete the return part, the code works.
this part:
AddItem:(state,action)=>{
if (!state.selectedItems.find(item => item.id === action.payload.id)) {
state.selectedItems.push({
...action.payload,
quantity: 1
})
}
return {
...state,
selectedItems: [...state.selectedItems],
...sumItems(state.selectedItems),
checkout: false
}
},
Don't both modify the state object state.selectedItems.push and use a return.
Option A - Modify Draft
AddItem: (state,action) => {
// Check if item exists
if (!state.selectedItems.find(item => item.id === action.payload.id)) {
// add item since it did not exist.
state.selectedItems.push({
...action.payload,
quantity: 1
})
}
// Calculate new count and totals.
const sum = sumItems(state.selectedItems);
// Apply new count and totals to the state.
state.itemsCounter = sum.itemsCounter;
state.totalPrice = sum.totalPrice;
// Set checkout to false.
state.checkout = false;
}
Option B - New State
AddItem: (state,action) => {
// create a new array containing selected items.
let newItems = [
...state.selectedItems;
];
// check if item already exists
if (!newItems.find(item => item.id === action.payload.id)) {
// add item since it did not exist
newState.selectedItems.push({
...action.payload,
quantity: 1
});
}
// return a new state
return {
...state,
selectedItems: newItems,
...sumItems(newItems),
checkout: false
}
}
You should probably stick with modifying the draft method unless you intend to replace the state with a completely new state. Using the draft method Immer should handle the reference updates where needed and allows you to write code as if you were editting a normal mutable object (for the most part, see the docs for some limitations).
I am building an eCommerce app, and with the below code I'm implementing the add to cart functionality.
The tricky part is when I try to add an item to cart that's already added with a different count.
So in the below code, I'm not
able to understand "if(existItem) {...}" part. This block is supposed to handle the above mentioned
case.
import {
CART_ADD_ITEM,
} from '../constants/cartConstants'
export const cartReducer = (state = {
cartItems: []
}, action) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload
const existItem = state.cartItems.map(x => x.product === item.product)
if (existItem) {
return {
...state,
cartItems: state.cartItems.map(x => x.product === existItem.product ? item : x)
}
} else {
return {
...state,
cartItems: [...state.cartItems, item]
}
}
default:
return state;
}
}
This is not a good test. It will be true even when empty
const cartItems = []
const existItem = cartItems.map(x => x.product === item.product)
if (existItem) console.log("Truthy")
Instead:
const cartItems = []
const existItem = cartItems.map(x => x.product === item.product)
if (existItem.length>0) console.log("Items")
else console.log("Empty")
Or faster because it returns immediately if found:
const cartItems = [{product:"fish"}]
const item = { product:"fish" }
const existItem = cartItems.find(x => x.product === item.product)
if (existItem) console.log("Item found")
else console.log("Empty")
This is the code that I used in the reducer to add items to cart, the return statement below is what I was trying to use to not allow any duplicate items in the cart but to change only the quantity of the item. But it still did not work. The quantity does update but I still end up with duplicate values. So please help me figure this out, Thx
export const initialState = {
cart: []
}
const Reducer = (state = initialState, action) =>{
switch(action.type){
case "ADD_TO_CART":
const {id, quantity} = action.item
let alreadyInCart = false, newQty=0
state.cart.forEach(x =>{
if(x.id === id){
alreadyInCart = true
newQty = parseInt(x.quantity+quantity)
}
})
//let categories = [...new Set(Inventory.map(x=>x.category))]
if(!alreadyInCart){
return {
...state,
cart: [...state.cart, action.item]
}
} else{
***return {
...state,
cart: [...state.cart,
{...action.item, quantity: newQty}
]
}***
}
default:
return state
}
}
export default Reducer
Looks like you just need to update the existing item in the cart, or add it to the cart.
Try this.
export const initialState = {
cart: [],
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
const nextCart = [...state.cart];
const existingIndex = nextCart.findIndex((item) => item.id === action.item.id);
if (existingIndex >= 0) {
const newQuantity = parseInt(nextCart[existingIndex].quantity + action.item.quantity);
nextCart[existingIndex] = {
...action.item,
quantity: newQuantity,
};
} else {
nextCart.push(action.item);
}
return {
...state,
cart: nextCart,
};
default:
return state;
}
};
export default Reducer;
The main problem is about react-native redux/javascript in general.
I want to create a special kind of "Todo-list", which means, that there are parent categories (in this case PackingLists) and each of them includes a name, two dates and the item array.
That means:
We assume, that we have a list of "Packinglists" (e.g. Madrid, Paris and Berlin).
It should be possible to add items to each of them (e.g. Madrid contains a Jeans and Shirt and Paris a skirt & a pair of shoes). The idea behind the result should be clear now!
There is no problem in the creation of the Packinglists. I just donĀ“t know how to "append"/modify the "Packinglist" and add/remove items from the array in each list with react-redux (in PackingListReducer.js - the problem is in add_ItemToPackingList) . Can you help me?
index.js (actions)
export const addPackingList = (name, startDate, endDate, items) => ({
type: 'add_PackingList',
payload: {
name,
startDate,
endDate,
items
}
});
export const addItemToPackingList = (parentItem, itemName, isDone) => ({
type: 'add_ItemToPackingList',
payload: {
parentItem,
itemName,
isDone,
}
});
export const removePackingList = (name) => ({
type: 'remove_PackingList',
payload: name
});
PackingListReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'add_PackingList':
return [...state, action.payload];
case 'add_ItemToPackingList': {
const currentList =
state.filter(packingList => packingList.name === action.payload.parentItem);
console.log(state);
console.log(currentList);
//return { ...state, items: [...currentList, action.payload] };
return Object.assign(state, { items: [...currentList, action.payload] });
}
case 'remove_PackingList':
return state.filter(packinglist => packinglist.name !== action.payload);
default:
return state;
}
};
Here I create the parent "PackingList", which works fine.
<TouchableOpacity
onPress={() => {
const exists = this.props.packingList.includes(
this.props.packingList.find(item => item.name === this.state.text));
console.log(endDate);
if (!exists) {
this.props.addPackingList(this.state.text, startDate, endDate, []);
this.props.navigation.goBack();
}
}
}
>
<Text style={styles.buttonStyle}>
CREATE NEW LIST!
</Text>
</TouchableOpacity>
</View>
The creation of the "item", which should be added to the parent packing list.
<TextInput
style={styles.inputText}
onSubmitEditing={() => {
this.props.addItemToPackingList(this.props.route.params.name,
this.state.text, false);
}}
</TextInput>
Try this:
Edited
// PackingListReducer.js
const currentListIndex =
state.findIndex(packingList => packingList.name === action.payload.parentItem);
let currentList = state[currentListIndex];
currentList = { ...currentList, items: [...currentList.items, action.payload] };
const newState = [...state];
newState[currentListIndex] = currentList;
...
return newState;
Updated
To delete the same items, reducer case would look like this (given the fact that you send itemName to your reducer in a payload):
case 'remove_ItemFromPackingList': {
const currentListIndex =
state.findIndex(packingList => packingList.name === action.payload.parentItem);
let currentList = state[currentListIndex];
currentList = { ...currentList, items: currentList.items.filter(item => item.itemName !== action.payload.itemName) };
const newState = [...state];
newState[currentListIndex] = currentList;
return newState;
}