I am trying to create a cart function, where I already have a way to create the cart in local storage with JSON. The problem arises when I try to add a way for the user to change the quantity of each product in the cart.
The handleItemQuantityChange
function works, but only after the user has clicked the change quantity buttons twice. This causes the local storage to only update after the function runs twice, therefore when the user refreshes the page after only clicking change once, it will not be updated. Im new to react so bare with me please.
Here is my cart page:
import PrimaryButton from '../components/PrimaryButton';
import './Cart.css';
import React, { useState, useEffect } from 'react';
export default function Cart() {
const [cartEmpty, setCartEmpty] = useState('');
const [cart, setCart] = useState(JSON.parse(localStorage.getItem('cart')));
const initialQuantity = {}; // Object to store previous quantities
const handleItemQuantityChange = (itemId, newQuantity) => {
setCart(prevCart =>
prevCart.map(item => (item.id === itemId ? { ...item, quantity: newQuantity } : item))
);
initialQuantity[itemId] = newQuantity; // Update previous quantity
localStorage.setItem('cart', JSON.stringify(cart));
};
useEffect(() => {
setInterval(() => {
if (localStorage.getItem('cartAmount') === '0') {
setCartEmpty('empty');
} else {
setCartEmpty('');
}
}, 1000);
}, []);
return (
<main id='main' className="cart">
<h1 className='title'>Cart</h1>
<section className={`cart-items ${cartEmpty}`}>
{cartEmpty === 'empty' ?
<>
<span>No items in cart</span><PrimaryButton
to={'/products'}
navButton={true}
text={'Shop Now'}
/>
</>
:
<>
{cart.map((item) =>
<div className='cart-item' key={item.id}>
<img
src={item.image}
alt={item.title}
/>
<div className='cart-item-info'>
<h2 className='cart-item-title'>{item.title}</h2>
<div className='cart-item-controls'>
<span className='cart-item-price'>${item.price}</span>
<div className="quantity">
<button
onClick={() => handleItemQuantityChange(item.id, item.quantity - 1)}
aria-label="Decrease quantity"
>-</button>
<input
value={initialQuantity[item.id] || item.quantity}
min={1}
max={100}
onChange={e => {
const newQuantity = parseInt(e.target.value, 10) || 1;
handleItemQuantityChange(item.id, newQuantity);
}}
aria-label={"Quantity is " + item.quantity} type="number"
name="quantity"
/>
<button
onClick={() => handleItemQuantityChange(item.id, item.quantity + 1)}
aria-label="Increase quantity"
>+</button>
</div>
</div>
</div>
</div>
)}
</>
}
</section>
</main>
)
};
Im sure that my code has a bunch of redundant pieces so if you want to correct me on them as well that is okay, but don't feel obligated to. Thanks in advance for anyone that answers.
The problem is with the asynchronous nature of setCart
inside the handleItemQuantityChange fn. It is very likely that by the time execution reaches localStorage.setItem
, the cart
variable isn't updated yet.
A workaround can be
const handleItemQuantityChange = (itemId, newQuantity) => {
const newCart = cart.map(item => (item.id === itemId ? { ...item, quantity: newQuantity } : item))
setCart(newCart);
initialQuantity[itemId] = newQuantity;
localStorage.setItem('cart', JSON.stringify(newCart));
};
The prevCart => ..
works when you want to serialize multiple state updates, it doesn't have an await like effect with synchronous code.