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")
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 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],
};
}
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;
In redux I have an array of selectedItems and changedItems
selectedItems: ["element 0","element 1","element 2"]
changedItems: ["element 6","element 7","element 8"]
<Button onClick={() => this.props.addItem(this.props.changedItems)}>save</Button>
I have a button that fires the addItem action but this is pushing 1 element into the selectedItems array from the changedItems array instead of all 3 elements and also I would like it to completely remove any existing elements in the selectedItems array when this is done
How can I push all elements from the changedItems array into the selectedItems array and overwrite the existing items in the selectedItems array?
actions.js
export const addItem = (item) => ({
type: ADD_ITEM,
item,
})
export const removeItem = (item) => ({
type: REMOVE_ITEM,
item,
})
export const clearItems = () => ({
type: CLEAR_ITEMS,
})
export const addChangedItem = (item) => ({
type: ADD_CHANGED_ITEM,
item,
})
export const removeChangedItem = (item) => ({
type: REMOVE_CHANGED_ITEM,
item,
})
selectedItemReducer.js
import {
ADD_ITEM, CLEAR_ITEMS, REMOVE_ITEM,
} from '../Constants'
const selectedItemReducer = (state = [], action) => {
switch (action.type) {
case ADD_ITEM:
return [
...state,
action.item,
]
case REMOVE_ITEM:
return state.filter((item) => item !== action.item)
case CLEAR_ITEMS:
return []
default:
return state
}
}
export default selectedItemReducer
changedItemReducer.js
import {
ADD_CHANGED_ITEM, REMOVE_CHANGED_ITEM,
} from '../Constants'
const changedItemReducer = (state = [], action) => {
switch (action.type) {
case ADD_CHANGED_ITEM:
return [
...state,
action.item,
]
case REMOVE_CHANGED_ITEM:
return state.filter((item) => item !== action.item)
default:
return state
}
}
export default changedItemReducer
If changedItems is guaranteed to always be an array, you should return those values in the reducer. From the looks of your changedItemReducer this appears to be the case.
const selectedItemReducer = (state = [], action) => {
switch (action.type) {
case ADD_ITEM:
return action.item;
case REMOVE_ITEM:
return state.filter((item) => item !== action.item)
case CLEAR_ITEMS:
return []
default:
return state
}
}
If not always an array then check first. If an array, return it, if not an array, place item into an array and return it.
const selectedItemReducer = (state = [], action) => {
switch (action.type) {
case ADD_ITEM:
return Array.isArray(action.item) ? action.item : [item];
case REMOVE_ITEM:
return state.filter((item) => item !== action.item)
case CLEAR_ITEMS:
return []
default:
return state
}
}
In both cases you are "throwing away" the old state when saving (returning) the new "changed items".