Search code examples
javascriptreactjsreact-hookslocal-storage

Updating local-storage based on mapped components in React


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.


Solution

  • 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.