Update object inside array in useState - javascript

I have this cart state in which the initial value is an empty array [].
const [cart,setCart] = useState([]);
This is how one of my product object looks like,
{id: 1, name: 'Shoe pair', price: 40}
There is an add to cart button for each product. So when the add to cart button is clicked addToCart function is triggered,
const addToCart = (item) => {
let initialItem = {id: item.id, name: item.name, quantity: 1}
let existingItem = cart.filter(cartItem => item.id === cartItem.id);
if(existingItem.length > 0){
existingItem.quantity = existingItem.quantity + 1;
} else {
setCart(crr => [...crr, initialItem ]);
}
}
What does addToCart do?
As you can see it is simple.First, it creates an object by setting the initial quantity to 1. If already the same product is present in the cart it updates the quantity in the cart product by 1 else the initialItem being added to the cart.
To monitor this occurrence I used useEffect hook,
useEffect(() => {
console.log(cart);
}, [cart]);
My problem is I can't see the cart in the console log when the quantity updates by 1 , But it shows when the initialItem is being pushed to the cart.

First issue: It is find, not filter.
Next issue - modifying item inside of array will not tell React that array is changed, you need to re-set state after existing item update also.
const addToCart = (item) => {
const initialItem = { id: item.id, name: item.name, quantity: 1 };
const existingItem = cart.find((cartItem) => item.id === cartItem.id);
if (existingItem) {
existingItem.quantity += 1;
setCart((curr) => [...curr]);
} else {
setCart((curr) => [...curr, initialItem]);
}
};

The reason your useEffect is not running when you think it should, is because its dependency is not being updated when you think it is. It will run when setCart is called and the reference to cart is updated, and then you will see your console log.
filter returns a new array -- will not mutate the original array.
docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
find returns the item by reference, if found -- otherwise returns undeifined.
docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Alternate example:
const addToCart = (item) => {
const existingItem = cart.find(i => i.id === item.id)
const updatedCart = existingItem
? cart.map(i => {
return i.id === item.id ? {...i, quantity: i.quantity + 1} : i
})
: [...cart, item]
}
setCart(updatedCart)
}

Related

Reactjs/Redux - Objects are not valid as a React child

I am making a shopping cart - onClick I have redux adding items to cartItems array.
In the code below (increment reducer its the last one after add/remove) I am trying to get rid of duplicate values from the cartItems array which holds all the items added to the shopping cart, and display a total number of unique items in the cart with cartIcon: {value: 0} - which is by default 0 (before adding any items).
const initialState = {
cartItems: [],
cartQuantity: 0,
cartIcon: {value: 0},
}
export const addToCartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
add(state, action ) {
const itemIndex = state.cartItems.findIndex(
(props) => props.id === action.payload.id
);
if(itemIndex >= 0){
state.cartItems[itemIndex].cartQuantity += 1;
} else {
const tempProduct = {...action.payload, cartQuantity: 1}
state.cartItems.push(tempProduct);
}
},
remove(state, action) {
const removeItem = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload.id
);
state.cartItems = removeItem;
},
increment: (state) => {
const Items = state.cartItems.filter(
(element, index) => state.cartItems.indexOf(element) === index);
state.value = Items.length;
} // if i just do state.value += 1
// then the value goes up by 1
// but I want to display the amount of unique entries
},
});
Here onClick I am pulling data from the item that was "added" to the cart and additionally trying to increment the cartIcon number by 1 (if the item hasn't been yet added to the array cartItems). The problem could be here? Because the error mentions all the props and data I'm pulling to be rendered into the cart.
const dispatch = useDispatch()
const handleAddToCart = (props) => {
dispatch(add(props));
};
return (<>
<div id={props.id} className='shopitem'>
<img src={props.url} />
<h2>{props.title}</h2>
<p className='boldprice'>${props.price}</p>
<button onClick={() => {
handleAddToCart(props);
dispatch(increment())
}}> ADD TO CART </button>
</div>
</>
)
}
And here I am trying to display the amount of unique items to the shopping cart icon.
const count = useSelector((state) => state.cart.cartIcon.value)
{count}
For some reason I am getting this error. If I just do state.value += 1 it will add +1 to the shopping cart icon, however I only want to display +1 for each unique item.
"Uncaught Error: Objects are not valid as a React child (found: object with keys {id, title, price, url, cartQuantity}). If you meant to render a collection of children, use an array instead."
Please help - I am relatively new to Javascript and programming overall.. I may be making a stupid mistake, so if something is clearly wrong.. then please let me know :)

Having problem with IncreaseItem function

My result is seeing item.acf.count as string and not as number. How can I convert this to number?
Here is the function below.
increaseItem = (id) => {
const { cart } = this.state;
cart.forEach(item => {
if (item.id === id) {
item.acf.count += 1
}
})
this.setState({
cart:cart
})
}
My result is seeing item.acf.count as string and not as number. please
how can I convert is to number
You should ensure that the initial item.acf.count state is a number type so this count: item.acf.count + 1 operation works correctly and returns a number type. So long as your state updaters maintain the state invariant of item.acf.count being a number it should work as expected.
Additionally, the increaseItem handler is mutating the cart state and not creating new array/object references.
increaseItem = (id) => {
const { cart } = this.state; // <-- cart is reference to cart state
cart.forEach(item => {
if (item.id === id) {
item.acf.count += 1; // <-- mutation!
}
});
this.setState({
cart: cart // <-- cart state reference back into state
})
}
You should instead shallow copy the cart and then also shallow copy the cart item (any any other nested properties) you want to update. I also suggest using a functional state update so you are correctly updating from the previous state and not any state value closed over in increaseItem callback scope.
increaseItem = (id) => {
this.setState(prevState => ({
cart: prevState.cart.map(item => item.id === id
? {
...item,
acf: {
...item.acf,
count: item.acf.count + 1
}
}
: item),
}));
}

React Redux update item quantity (more than just one increment)

I have a site where the user can increase the quantity on the product before adding it to cart. Now if the user decided to go back to the product and add 3 more by increasing the quantity on the product, then adding to cart - how do I update the quantity of the existing product in basket?
At the moment I get duplicates of the product with different quantities depending on what is selected.
Here is the code I have for my reducer:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
items: [],
};
const basket = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, { payload }) => {
// No idea what to do with this..
state.items.filter((pizza) => pizza.name === payload.name);
// This pushes the item fine, but I get multiple of the same item in the cart instead of just updating its quantity
state.items.push(payload);
// state.items.map((pizza) =>
// pizza.name === payload.name
// ? {
// ...pizza,
// quantity: pizza.quantity + payload.quantity,
// }
// : pizza
// );
},
},
});
export const { addToBasket } = basket.actions;
export const basketItems = (state) => state.basket.items;
export default basket.reducer;
The payload is the specific product, it will be an object:
{
name: "product name",
image: "url.jpeg",
price: "14.99"
}
I can not for the life of me figure out what to do here in order not to mutate the state. Nothing works, I feel like I have tried every possible way but clearly I am missing something.
Any help much appreciated!!!
Thanks
You actually have all the code you need, just need it applied correctly. First check that the item is already in the items array or not. If it is already there then copy the existing state and update the matching element. If it is not included then append the new item to the end of the array.
const basket = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, { payload }) => {
const item = state.items.find((pizza) => pizza.name === payload.name);
if (item) {
state = state.items.map((pizza) =>
pizza.name === payload.name
? {
...pizza,
quantity: pizza.quantity + payload.quantity
}
: pizza
);
} else {
state.items.push(payload);
}
},
},
});
With Redux-Toolkit you can mutate the state objects so you can likely simplify this a bit. Instead of mapping a new array and setting it back to state, jut mutate the found object.
const basket = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, { payload }) => {
const item = state.items.find((pizza) => pizza.name === payload.name);
if (item) {
item.quantity += payload.quantity;
} else {
state.items.push(payload);
}
},
},
});

After removing an item from an array it removes all duplicates entries - Redux

I am making a shopping cart app with react-redux , and i am facing a problem. Every item in my list has an ID , but when I add 2 items with the same ID and then I suddenly removes them from the cart , all the items with the same id (aka duplicates are removed.Code below.
Reducer.js
const reducer = (state = { items: [] }, { type, payload }) => {
console.log(payload);
switch (type) {
case "ADD_ITEM":
return {
...state,
items: [...state.items, payload]
};
case "REMOVE_ITEM":
const index = payload.index;
return {
...state,
items: state.items.filter(({ id }) => id !== payload.id)
};
case "DUPLICATE_ITEM":
return {
...state
};
default:
return state;
}
};
Action.js
const addItem = item => ({
type: "ADD_ITEM",
payload: item
});
const removeItem = (item) => ({
type: "REMOVE_ITEM",
payload: item
});
const duplicateItem = () => ({
type: "DUPLICATE_ITEM"
});
export { addItem, removeItem, duplicateItem };
Item.js
const items = [
{
id: 1,
name: "projectile",
price: "150$",
units: 0
},
{
id: 2,
name: "cute cat",
price: "1250$",
units: 0
}]
Sounds to me like the issue is more of a design problem i.e. rather than adding N of the same item to the cart, you probably want to increase the units/qty of the item if it already exists? Then when you remove, decrease the units/qty until it's reduced to 1 and then remove.
If you want the behaviour to work as is you would need to change how you remove from the cart e.g.
remove by index rather than by ID
create surrogate IDs for items when they are added to the cart and then remove by this

Transfer objects between two states in React

I'm creating a shopping cart with react hooks that looks like this
const ShoppingCart = () => {
const [products, setProducts] = useState([
{id: 1, name: 'Product 1', price: 2500},
{id: 2, name: 'Product 2', price: 2000},
{id: 3, name: 'Product 3', price: 2500},
])
const [cart, setCart] = useState()
const addToCart = (item) => (
setCart([cart, {item}])
// setCart([...cart, {item}])
// setCart([{item}])
)
return (
{
products.map((product, i) => (
<>
<Product name={product.name} price={product.price} key={product.id} />
<button
value='Add to cart'
// onClick={((product) => setCart(...cart, product))}
onClick={(product) => addToCart(product)}
// onClick={console.log(i)}
/>
{console.log(cart)}
</>
))
}
)
}
The lines commented out are some of the things I had tried that didn't work. Also tried it without the second arg[i] in the lamda function within map.
I'm trying to add a product to the cart object after a user clicks the button
The expected state of cart for example if the user clicks the button next to the product with id of one would be
[{id: 1, name:'Product 1', price: 2500}]
If it would be possible, I would also like to add another field to the object which would be amount
Set the initial value of the cart
const [cart, setCart] = useState([]); // [] is the initial value of the cart
remove product from the callback passed to onClick because it's not the product, it's the click event
<button value="Add to cart" onClick={() => addToCart(product)} />
and the spread operator should work fine
const addToCart = item => {
setCart([...cart, item]);
};
EDIT
if you want to add the item only once, check if it exists in the cart first :
const addToCart = item => {
const ndx = cart.findIndex(e => e.id === item.id);
if (ndx === -1) {
setCart([...cart, item]);
}
};
please use ...cart in your addToCart function because cart would be previous data array and when you do like that [...cart,item] then Previous array would merge in new array and will add new product object in that new array.
const addToCart = (item) => (
setCart([...cart,item])
)
and please don't pass param to your click callback function because you are getting this product from above not from click function .use anonymous function like this
onClick={() => addToCart(product)}
use onClick={((product) => setCart([...cart, product]))}

Categories

Resources