Search code examples
reactjsnext.jslocal-storagereact-context

Problem with saving quantity to localStorage


I'm making a shopping cart and want to show the quantity of all items in the header of my website.

I use useReducer and context to store my items through my applicationand this works. But when I refresh everything is gone. So now I wanna store my quantity in the localStorage.

So if I add a new item to the card I check if quantity already excists in localStorage and add my new quantity to it. But somehow everything runs twice resulting in adding the double amount of intended.

sorry for my grammar english is not my first language

import { useReducer } from "react";

import CartContext from "./cart-context";

const defaultCartState = {
  items: [],
  totalAmount: 0,
  totalQuantity: 0,
};

const cartReducer = (state, action) => {
  if (action.type === "ADD") {
    const updatedTotalAmount =
      state.totalAmount + action.item.price * action.item.amount;

    if (JSON.parse(localStorage.getItem("quantity"))) {
      state.totalQuantity = JSON.parse(localStorage.getItem("quantity"));
    }

    console.log(state.totalQuantity);
    console.log(action.item.amount);

    const updatedTotalQuantity = state.totalQuantity + action.item.amount;

    const existingCartItemIndex = state.items.findIndex(
      (item) => item.id === action.item.id
    );
    const existingCartItem = state.items[existingCartItemIndex];
    let updatedItems;

    if (existingCartItem) {
      const updatedItem = {
        ...existingCartItem,
        amount: existingCartItem.amount + action.item.amount,
      };
      updatedItems = [...state.items];
      updatedItems[existingCartItemIndex] = updatedItem;
    } else {
      updatedItems = state.items.concat(action.item);
    }

    localStorage.setItem("quantity", JSON.stringify(updatedTotalQuantity));

    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount,
      totalQuantity: updatedTotalQuantity,
    };
  }

  return defaultCartState;
};

CartProvider

const CartProvider = (props) => {
  const [cartState, dispatchCartAction] = useReducer(
    cartReducer,
    defaultCartState
  );

  const addItemToCartHandler = (item) => {
    dispatchCartAction({ type: "ADD", item: item });
  };

  const removeItemFromCartHandler = (id) => {
    dispatchCartAction({ type: "REMOVE", id: id });
  };

  const cartContext = {
    items: cartState.items,
    totalAmount: cartState.totalAmount,
    totalQuantity: cartState.totalQuantity,
    addItem: addItemToCartHandler,
    removeItem: removeItemFromCartHandler,
  };

  return (
    <CartContext.Provider value={cartContext}>
      {props.children}
    </CartContext.Provider>
  );
};

export default CartProvider;

The console where you see it running twice: Screenshot of console


Solution

  • try to set the value from localstorage only once when you create a context and pass a default state to it like this:

    import { useContext, useReducer, createContext } from 'react'
    
    const defaultCartState = {
      items: [],
      totalAmount: 0,
      totalQuantity: JSON.parse(localStorage.getItem('quantity')) ?? 0,
    }
    
    const CartContext = createContext(defaultCartState)
    
    const cartReducer = (state, action) => {
      if (action.type === 'ADD') {
        const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount
    
        const updatedTotalQuantity = state.totalQuantity + action.item.amount
    
        const existingCartItemIndex = state.items.findIndex((item) => item.id === action.item.id)
        const existingCartItem = state.items[existingCartItemIndex]
        let updatedItems
    
        if (existingCartItem) {
          const updatedItem = {
            ...existingCartItem,
            amount: existingCartItem.amount + action.item.amount,
          }
          updatedItems = [...state.items]
          updatedItems[existingCartItemIndex] = updatedItem
        } else {
          updatedItems = state.items.concat(action.item)
        }
    
        localStorage.setItem('quantity', JSON.stringify(updatedTotalQuantity))
    
        return {
          items: updatedItems,
          totalAmount: updatedTotalAmount,
          totalQuantity: updatedTotalQuantity,
        }
      }
    
      if (action.type === 'REMOVE') {
        const existingCartItem = state.items.find((item) => item.id === action.id)
        console.log('stas', action.id, existingCartItem)
        const updatedTotalAmount = state.totalAmount - existingCartItem.price * existingCartItem.amount
    
        const updatedTotalQuantity = state.totalQuantity - existingCartItem.amount
    
        const updatedItems = state.items.filter((item) => item.id !== action.id)
    
        localStorage.setItem('quantity', JSON.stringify(updatedTotalQuantity))
    
        return {
          items: updatedItems,
          totalAmount: updatedTotalAmount,
          totalQuantity: updatedTotalQuantity,
        }
      }
    
      return defaultCartState
    }
    
    const CartProvider = (props) => {
      const [cartState, dispatchCartAction] = useReducer(cartReducer, defaultCartState)
    
      const addItemToCartHandler = (item) => {
        dispatchCartAction({ type: 'ADD', item: item })
      }
    
      const removeItemFromCartHandler = (id) => {
        dispatchCartAction({ type: 'REMOVE', id: id })
      }
    
      const cartContext = {
        items: cartState.items,
        totalAmount: cartState.totalAmount,
        totalQuantity: cartState.totalQuantity,
        addItem: addItemToCartHandler,
        removeItem: removeItemFromCartHandler,
      }
    
      // eslint-disable-next-line react/prop-types
      return <CartContext.Provider value={cartContext}>{props.children}</CartContext.Provider>
    }
    
    const My = () => {
      const { addItem, items, removeItem, totalAmount, totalQuantity } = useContext(CartContext)
      console.log('context', addItem, items, removeItem, totalAmount, totalQuantity)
      return (
        <div>
          <div>
            items:{' '}
            {items.map((item, i) => (
              <div key={i}>{JSON.stringify(item)}</div>
            ))}
          </div>
          <div>totalAmount: {totalAmount}</div>
          <div>totalQuantity: {totalQuantity}</div>
          <button onClick={() => addItem({ price: 3, amount: 1, id: 2 })}>add</button>
          <button onClick={() => removeItem(2)}>remove</button>
        </div>
      )
    }
    const App = () => {
      return (
        <CartProvider>
          <My />
        </CartProvider>
      )
    }
    
    export default App