Search code examples
javascriptreactjsreduxnext.js

Add item if it isn't already in the cart, increase its quantity when it does


I'm using React context to add products to the cart with simple logic, and I'm using useReducer hook for adding items to the cart.

With Redux Toolkit the logic is easy to implement, firstly please check my redux logic:

const cartItemSlice = createSlice({
    name: "cart",
    initialState: {
        items: [],
        totalQuantity: 0,
        totalAmount: 0,
    },
    reducers: {
        addProduct(state, action) {
            const newCartItems = action.payload; //items from dispatch object
            const existingItem = state.items.find((el) => el.id === newCartItems.id);
            state.totalQuantity++;
            if (!existingItem) {
                state.items.push({
                    id: newCartItems.id,
                    quantity: 1,
                    title: newCartItems.title,
                    price: newCartItems.price,
                    image: newCartItems.image,
                    totalPrice: newCartItems.price,
                });
            } else {
                existingItem.quantity++;
            }
        },
    }

 })

Now HOW CAN I DO THAT SAME THING WITH REACT CONTEXT? How to get the array that is matched with the id and add the item to the cart while dispatching it, here I'm trying to put that in context:

import React, { useReducer } from "react";

const CartItemContext = React.createContext();

const CartContextProvider = ({ children }) => {
    const [state, dispatch] = useReducer(
        (state, action) => {
            if (action.type === "ADD-ITEM") {
                return {
                    ...state,
                    items: state.items.find((el) => el.id === action.payload.id),
                    totalQuantity: state.totalQuantity++,
                };
            }
        },
        {
            items: [],
            totalPrice: 0,
            totalQuantity: 0,
        }
    );
    return (
        <CartItemContext.Provider
            value={{
                items: state.items,
                totalPrice: state.totalPrice,
                totalQuantity: state.totalQuantity,
                dispatch,
            }}
        >
            {children}
        </CartItemContext.Provider>
    );
};

export { CartItemContext, CartContextProvider };

And here when I click the add to cart button in my component:

export default function BookItems(props) {
    const { id, title, price, image } = props;

    const cartItemsCtx = useContext(CartItemContext);
    const { dispatch } = cartItemsCtx;

    const addToCartHandler = () => {
        dispatch({
            type: "ADD-ITEM",
            payload: {
                id, title, price, image 
            },
        });
    };
    return (
        <div className={classes.product__item}>                     
            <Link href={`/products/${id}`}>
                <Image src={image} alt={title} className={classes.image} fill />
                <h3>{title}</h3>
            </Link>
            <div className={classes.bottom}>
                <p>${price}</p>
                <button onClick={addToCartHandler}>Add to cart</button>
            </div>              
        </div>
    );
}

Solution

  • You can do it as below, with the help of the Spread syntax. If there is an existing one, change its quantity and add it to state.items; otherwise, add the new one.

    import React, { useReducer, createContext } from "react";
    
    // I moved it out here for readability
    const reducer = (state, action) => {
      if (action.type === "ADD-ITEM") {
        const newCartItems = action.payload;
        let existingItem = state.items.find((el) => el.id === newCartItems.id);
        if (existingItem) {
          existingItem = { ...existingItem, quantity: existingItem.quantity + 1 };
        }
        return {
          ...state,
          items: !existingItem ? [...state.items, newCartItems] : [...state.items, existingItem],
          totalQuantity: state.totalQuantity++,
        };
      }
    };
    
    // I moved it out here for readability
    initialState = {
      items: [],
      totalPrice: 0,
      totalQuantity: 0,
    };
    
    const CartItemContext = createContext();
    
    const CartContextProvider = ({ children }) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <CartItemContext.Provider
          value={{
            items: state.items,
            totalPrice: state.totalPrice,
            totalQuantity: state.totalQuantity,
            dispatch,
          }}
        >
          {children}
        </CartItemContext.Provider>
      );
    };
    
    export { CartItemContext, CartContextProvider };