++ increments value by 2 instead of 1 - reactjs

I am using the following reducer to add products, delete them, and add them to a cart. I would like the 'ADD_TO_CART' method when called to add an item to the cart, but if an item is already in the cart, I would like to increase it by 1. However, the method is increasing cart items by 2 instead of 1
export default (state, action) => {
switch (action.type) {
case 'ADD_PRODUCT':
return {
...state,
products: [action.payload, ...state.products]
}
case 'DELETE_PRODUCT':
return {
...state,
products: state.products.filter(product => product.id !== action.payload)
}
case 'ADD_TO_CART': if (state.cart.some(cartItem => cartItem.id === action.payload.id)) {
const updatedItemIndex = state.cart.findIndex(cartItem => cartItem.id === action.payload.id)
let cartItems = [...state.cart]
let itemToUpdate = cartItems[updatedItemIndex]
itemToUpdate.quantity++
return { ...state, cart: cartItems }
} else {
const newItem = { ...action.payload, quantity: 1 }
return { ...state, cart: [...state.cart, newItem] }
}
default:
return state
}
}
This is the code dispatching the action
function addToCart(product) {
dispatch({
type: 'ADD_TO_CART',
payload: product
})
}
I am calling the action from the following component
import React, { useContext } from 'react'
import { ProductContext } from './ProductContext'
export const ProductCard = ({ product }) => {
const { deleteProduct, addToCart } = useContext(ProductContext)
const handleDelete = () => {
deleteProduct(product.id)
}
const handleCart = () => {
addToCart(product)
}
return (
<div className='product__card'>
<h3>{product.productName}</h3>
<p>{product.price}</p>
<button className='button'>View Product</button>
<button onClick={handleCart} className='button'>Add to Cart</button>
<button onClick={handleDelete} className='button'>Delete</button>
</div>
)
}

Try changing the quantity from a number to an empty quote.
Old: const newItem = { ...action.payload, quantity: 1 }
New: const newItem = { ...action.payload, quantity: '' }

You have a mistake. You MUTATE your state.
let cartItems = [...state.cart] // <-- reinitialize only the array,
// the array items remains with the same reference!!!!
let itemToUpdate = cartItems[updatedItemIndex]
itemToUpdate.quantity++ // <--- this will mutates your state!!!
return { ...state, cart: cartItems }
Fix: create a new item, and copy the data with the spread operator, and delete the old one.

I have ran into the same problem with a very similar situation, and none of the current answers helped me. Therefore I can only recommend
itemToUpdate.quantity++
Be changed into
itemToUpdate.quantity += 0.5;
I know that this is not the correct way to solve this problem, but the functionality works, and you can change it later when you will find the solution

Related

react redux thunk not populating state object

im having an issue with my code, its not populating the state object when state action is being performed. im new with redux
i have this code. so far that having an issue
this is the statement that will called the props.action fetchProjectFamilyList
case 'SubBusinessUnit':
setProductFamilyDetailsObj([])
if (selectedOption.id != 0) {
props.actions.fetchDepartment(selectedOption.id)
props.actions.fetchProjectFamilyList(selectedOption.id)
console.log(props)
}
setDropdownDataInState(resetData, 'Department')
setFormFields({
...formFields,
'OtherNamedInsuredIndustry': {
...formFields.OtherNamedInsuredIndustry,
value: ''
},
'NamedInsuredIndustry': {
...formFields.NamedInsuredIndustry,
value: "",
selectedId: 0
},
[fieldName]: {
...formFields[fieldName],
value: selectedOption.description, selectedId: selectedOption.id
}
});
break;
and this is the code for the commonreducer
export const fetchProjectFamilyList = createAsyncThunk(types.FETCH_PROJECT_FAMILY_LIST,
async (option, {getState, rejectWithValue}) => {
const reduxThunkConfig = {
checkStateData:getState().commonReducer.projectFamilyList && getState().commonReducer.projectFamilyList[option],
rejectWithValue
}
const APIConfig = {
URL: "eapi-referencedata/v1/lists/12?filterBySourceList=" + option + "&filterBySourceListValue=15",
method:"getData",
}
console.log('fetchProjectFamilyList')
return fetchCachedData(reduxThunkConfig, APIConfig);
}
)
im using the builder in my case of course inistailstate is set
const initialState = {
projectFamilyList:{},
}
builder.addCase(fetchProjectFamilyList.fulfilled, (state, action) => {
const subDivision = action.meta.arg;
return {
...state,
projectFamilyList:{
...state.projectFamilyList,
[subDivision]: action.payload},
}})
const commonActions = { ...actions, fetchProjectFamilyList }
export { commonActions, commonReducer}
this is the comment that accept the state as props. but the props productFamilyDetailsObj is empty object
<ProductFamilyComponent
productFamilyDetailsObj={productFamilyDetailsObj}
/>
function ProductFamilyComponent({ productFamilyDetailsObj }) {
return <div className="boxLayout">
<p className="smallHeading">Product Families</p>
{productFamilyDetailsObj.map((text, textIndex) => {
let index = textIndex;
return ( .... and so on
I hope theres someone who could help me resolving this. thank in advance.

Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft

So, I am making this shopping cart in redux-toolkit, where user can't order more than the quantity available. So, my reducer function is like below for adding to cart.
import { createSlice } from "#reduxjs/toolkit";
import { FoodCartType } from "../../types";
type CartState = {
cart: FoodCartType[];
};
const initialState: CartState = {
cart: [],
};
export const foodcartSlice = createSlice({
name: "foodcart",
initialState,
reducers: {
addToCart: (state, action) => {
console.log(action);
if (state.cart.length === 0) {
state.cart.push(action.payload);
}
const itemIndex = state.cart.findIndex(
(item) => item.id === action.payload.id
);
if (itemIndex >= 0) {
return {
...state,
cart: state.cart.map((item) => {
if (item.id === action.payload.id) {
return {
...item,
quantity: item.quantity + 1,
quantity_available: item.quantity_available - 1,
};
}
}),
};
} else {
return {
...state,
cart: [
...state.cart,
{
...action.payload.product,
quantity: 1,
quantity_available: action.payload.quantity_available - 1,
},
],
};
}
},
},
});
export const { addToCart } = foodcartSlice.actions;
export default foodcartSlice.reducer;
When I click on the dispatch, on the component. I get the following error.
Unhandled Runtime Error Error: [Immer] An immer producer returned a
new value and modified its draft. Either return a new value or
modify the draft.
So, where I am doing the problem? if I just remove the itemIndex check part, items get pushed into the cart. But, other than that it is giving me this error. How should I rewrite this?
Because your state.cart.push(action.payload); line modifies the value of state and later you return a new value.
You are allowed to do one, but not both at once. See writing reducers with immer
What you could do here: always modify. That's also far more readable.
export const foodcartSlice = createSlice({
name: "foodcart",
initialState,
reducers: {
addToCart: (state, action) => {
console.log(action);
// you push later anyways, so you can also delete these lines
// if (state.cart.length === 0) {
// state.cart.push(action.payload);
// }
const itemIndex = state.cart.findIndex(
(item) => item.id === action.payload.id
);
if (itemIndex >= 0) {
state.cart[itemIndex].quantity += 1
state.cart[itemIndex].quantity_available -= 1
} else {
state.cart.push({
...action.payload.product,
quantity: 1,
quantity_available: action.payload.quantity_available - 1,
})
}
},
},
});

How do I check if an item exists and modify it or return the original object

export const basketReducer = (state = { total:0, items:[]}, action) => {
switch (action.type) {
case "ADD_ITEM":
const item = [...state.items, action.payload]
const updateBasket = state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return item
})
It seems your code is close.
First check if state.items array already contains some element with a matching _id property.
If there is a match then shallow copy the state and shallow copy the items array and update the matching element.
If there is no match then shallow copy the state and append the new data to the items array.
Reducer case logic:
case "ADD_ITEM":
const hasItem = state.items.some(el => el._id === action.payload._id);
if (hasItem) {
// update item
return {
...state:
items: state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return el; // <-- return current mapped element if no change
}),
};
} else {
// add item
return {
...state,
items: state.items.concat(action.payload),
};
}

cant add existing product in cart in reducer in redux with react native

am working on a react native application that has a maternal shop,am using redux to work on the global state,and in my reducers i have to handle adding to cart,if product has not been existing in the cart or if product exists in the cart, my aim is just to increase its quantity without increasing the total product count
here are my actions and reducers
export const ADD_TO_CART = 'ADD_TO_CART';
export const ADD_QUANTITY = 'ADD_QUANTITY';
export const SUB_QUANTITY = 'SUB_QUANTITY';
export const DELETE_ITEM = 'DELETE_ITEM';
export const addToCart = (payload) => {
type: ADD_TO_CART,
payload
}
export const addQuantity = (payload) => {
type: ADD_QUANTITY,
payload
}
export const subQuantity = (payload) => {
type: SUB_QUANTITY,
payload
}
export const deleteItem = (payload) => {
type: DELETE_ITEM,
payload
}
my reducers for the cart
import { ADD_TO_CART,DELETE_ITEM,SUB_QUANTITY,ADD_QUANTITY} from '../actions/cartItems';
const initialState = {
itemsCount : 0,
cartItems:[],
cartTotalAmount:0,
}
const cartReducer = (state=initialState, action)=>{
switch(action.type){
case ADD_TO_CART:
let cart = {
id:action.payload.itemId,
quantity:action.payload.quantity,
name:action.payload.itemTitle,
image:action.payload.itemImg,
price:action.payload.itemPrice,
cartAmount:action.payload.quantity * action.payload.itemPrice
}
if(state.itemsCount === 0){
state.cartItems.push(cart);//just push the cart
return {
...state,
itemsCount:state.itemsCount+1,
cartTotalAmount:state.cartItems.map(item => {
state.cartTotalAmount+item.cartAmount
})
}
}else{
let exists =false;
let i =0;
while(i<state.cartItems.length){
if(state.cartItems[i].id === action.payload.itemId){
state.cartItems[i].quantity++;
exists = true;
}
return{
...state,
itemsCount:state.itemsCount
}
i++;
}
state.cartItems.map((key,item) => {
if(item.id === action.payload.itemId){
// {...item,quantity:item.quantity+1}
state.cartItems[key].quantity++;
exists = true
}
return {
...state,
itemsCount:state.itemsCount,
}
})
if(!exists){
let _cart = {
id:action.payload.itemId,
quantity:action.payload.quantity,
name:action.payload.itemTitle,
image:action.payload.itemImg,
price:action.payload.itemPrice,
cartAmount:action.payload.quantity * action.payload.itemPrice
}
state.cartItems.push(_cart)
return {
...state,
itemsCount:state.itemsCount+1,
cartTotalAmount:state.cartItems.map(item => {
state.cartTotalAmount+item.cartAmount
})
}
}
}
case ADD_QUANTITY:
return {
...state,
cartItems:state.cartItems.map(
item => item.id === action.payload.itemId
? {...item, quantity: item.quantity+1 }
: item
),
}
case DELETE_ITEM:
let newCartItems = state.cartItems.filter(
(item) => {return item.id != action.payload.itemId}
)
let count = state.itemsCount-1;
return {
...state,
itemsCount:count,
cartItems:newCartItems,
}
case SUB_QUANTITY:
return {
...state,
cartItems:state.cartItems.map(
item => item.id === action.payload.itemId
? {...item, quantity: item.quantity-1 }
: item
),
}
// case ADD_TO_WISH_LIST:
// for(let i=0; i < state.wishListItems.length; i++){
// if(state.wishListItems[i].id === action.item.id){
// return {
// ...state,
// wishListItems: state.wishListItems.map(item => item.id === action.item.id ?
// { ...item, quantity: item.quantity+1 } :item
// ) ,
// }
// }
// else{
// let updatedWishListItems = [...state.wishListItems, action.item];
// let count = state.wishCount + 1;
// }
// }
// return{
// ...state,
// wishCount : count,
// wishListItems :updatedWishListItems
// }
// case DELETE_FROM_WISH_LIST:
// let newWishListItems = state.wishListItems.filter(
// (item)=>{
// return item.id!=action.item.id
// }
// );
// return {
// ...state,
// wishListItems : newWishListItems ,
// }
default:
return state
}
}
export default cartReducer;
the first case in the reducer for adding to cart when itemsCount === 0 works however when the cart has more than one item the reducer has failed to execute properly and am stuck help needed
Issues
Don't store the itemsCount and cartTotalAmount values in state, these are easily derived from state data. Storing duplicate or derived data is anti-pattern.
const initialState = {
itemsCount : 0, // <-- easily computed from cart items
cartItems:[],
cartTotalAmount:0, // <-- easily computed from cart items
}
Don't mutate your state by pushing directly into your cartItems array.
state.cartItems.push(cart); // <-- mutates state reference
You need to search the cartItems first to see if you've already added a cart item.
Solution
case ADD_TO_CART:
const {
itemId,
itemImg,
itemPrice,
itemTitle,
quantity,
} = action.payload;
// search if item is already in cart by item id
const inCart = state.cartItems.some(item => item.id === itemId);
if (inCart) {
// already in cart, shallow copy cart items
return {
...state,
cartItems: state.cartItems.map(item => item.id === itemId ? {
// found item, shallow copy item and update quantity property
...item,
quantity: item.quantity + 1,
} : item),
}
} else {
return {
...state,
cartItems: [
// shallow copy cart items
...state.cartItems,
// add new cart item
{
id: itemId,
quantity: quantity,
name: itemTitle,
image: itemImg,
price: itemPrice,
}
],
}
}
...
You can apply similar updating patterns for other action types that update the cart items.
To compute the itemsCount in your UI
const itemsCount = cartItems.reduce((count, { quantity }) => count + quantity, 0)
To compute the cartTotalAmount
const cartTotalAmount = cartItems.reduce(
(totalAmount, { price, quantity }) => totalAmount + quantity * price,
0,
);
These can be combined into a single calculation in a single pass
const { cartTotalAmount, itemsCount } = cartItems.reduce(
({ cartTotalAmount, itemsCount }, { price, quantity }) => ({
cartTotalAmount: cartTotalAmount + quantity * price,
itemsCount: itemsCount + quantity,
}),
{
cartTotalAmount: 0,
itemsCount: 0,
},
);

Delete an item from an array in Redux

I'm learning redux and I was wondering how to delete one item from the state. I have this initial state:
export const getInitialState = () => {
let state = {
isLogged: false,
organizations: [],
userData: {},
activeIndex: -1,
currentRetrospective: {},
hasFetched: false
}
This is how the data lives inside organizations
case `${actions.ACTION_GET_USER_ORGS}_FULFILLED`: {
let activeIndex = 0
if (state.activeIndex !== -1) {
activeIndex = state.activeIndex
} else if (action.payload.data.length === 0) {
activeIndex = -1
}
return { ...state, activeIndex, organizations: action.payload.data, hasFetched: true }
}
Now, what I need to do is to delete one item from the retrospectives array in an organization. I tried this but it doesn't work. Is there a better way to do it?
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
const { organizations, activeIndex } = state
const newOrganizations = JSON.parse(JSON.stringify(organizations))
const activeOrganization = newOrganizations[activeIndex]
activeOrganization.retrospectives = activeOrganization.retrospectives
.filter((retro) => retro.id != action.retroId )
return { ...state, organizations: newOrganizations }
}
Thank you!
you can filter the organization array like this:
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
return {
...state,
organizations: state.organization.filter(retro =>
retro.id !== action.retroId }
}

Resources