Search code examples
javascriptreactjsnext.jscartshopping-cart

Get selected item and its count


I am trying to make a shopping cart app like this site but using reactjs.

index.js: (Sending each product to product component)

        {products.length > 0
          ? products.map((product) => (
              <Product key={product.id} product={product} />
            ))
          : ""}

components/product.js:

<div>
    {product?.price}
   <h3>
      {product.name ? product.name : ""}
   </h3>
   <div dangerouslySetInnerHTML={{ __html: product?.description }} />
</div>

Also I have the ADD button UI switch code and that will look like,

Before clicking add button,

------------
| ADD     +|
------------

After clicking add button,

-----------------
| -  {count}   +|
-----------------

Things are fine as of now and the respective count(s) on each product individually gets added in home page.

Also with the help of contextApi made an AppContext and updated the cart like,

  const addToCart = (product) => {
    if (+cart >= 0) {
      setCart(+cart + 1);
      setCartItems(product);
    }
  };

In nav.js, I am getting the updated count of the products but when I click on the bag menu (Which is a cart page) , I am unable to fetch the context.

Expected Result

While visiting the cart page (On click of the bag menu in header), the page would display the so far selected product (with name and description) along with their quantity respectively.

Current Result:

Unable to get the appcontext data and display the selected product along with the quantity.

Working Snippet:

Edit Using Tailwind with Next.js (forked)

Please kindly help me to achieve the result of displaying the selected product and its respective quantity while navigating to cart (bag) page.. I am struggling with this for long.. So I humbly request you to help me with right solution.. Big thanks in advance..


Solution

  • Issues

    1. You've multiple AppProvider components each providing a different context to their wrapped children.
    2. The initial AppContext shape doesn't match what is actually passed as the context's value.
    3. The cartItems state isn't maintained as an array.
    4. Other various issues and patterns

    Solution

    1. Remove all the extraneous AppProvider provider components, use just one to wrap your entire app in _app.js.

      import { AppProvider } from "../components/context/AppContext";
      
      export default class TailwindApp extends App {
        render() {
          const { Component, pageProps } = this.props;
          return (
            <AppProvider>
              <Component {...pageProps} />
            </AppProvider>
          );
        }
      }
      
    2. Fix the context to have an initial value that matches the provided value. Fix the state updaters to correctly manage the cartItems array. Use an useEffect hook to compute the derived total item count state when the cartItems array state updates. Correctly add and update item/product quantities, and remove item when quantity reaches 0.

      import React, { useState, useEffect } from "react";
      
      export const AppContext = React.createContext({
        cart: 0,
        cartItems: [],
        setCart: () => {},
        addToCart: () => {},
        removeFromCart: () => {}
      });
      
      export const AppProvider = (props) => {
        const [cart, setCart] = useState(0);
      
        const [cartItems, setCartItems] = useState([]);
      
        useEffect(() => {
          setCart(cartItems.reduce((count, { quantity }) => count + quantity, 0));
        }, [cartItems]);
      
        const addToCart = (product) => {
          setCartItems((items) => {
            if (items.find(({ id }) => id === product.id)) {
              return items.map((item) =>
                item.id === product.id
                  ? {
                      ...item,
                      quantity: item.quantity + 1
                    }
                  : item
              );
            } else {
              return [
                ...items,
                {
                  ...product,
                  quantity: 1
                }
              ];
            }
          });
        };
      
        const removeFromCart = (product) => {
          setCartItems((items) => {
            const foundItem = items.find(({ id }) => id === product.id);
            if (foundItem?.quantity > 1) {
              return items.map((item) =>
                item.id === product.id
                  ? {
                      ...item,
                      quantity: item.quantity - 1
                    }
                  : item
              );
            } else {
              return items.filter(({ id }) => id !== product.id);
            }
          });
        };
      
        return (
          <AppContext.Provider value={{ cart, addToCart, removeFromCart, cartItems }}>
            {props.children}
          </AppContext.Provider>
        );
      };
      
    3. In Products.js remove all the local item count state, quantities can be accessed from state in the context. Conditionally render the "Add Item" and increment/decrement buttons on the item count.

      import Link from "next/link";
      import { useContext } from "react";
      import { AppContext } from "./context/AppContext";
      
      const Product = (props) => {
        const { product } = props;
        const contextData = useContext(AppContext);
      
        const count =
          contextData.cartItems.find(({ id }) => id === product.id)?.quantity ?? 0;
      
        const addToCart = (product) => () => {
          contextData.addToCart(product);
        };
        const removeFromCart = (product) => () => {
          contextData.removeFromCart(product);
        };
      
        return (
          ...
      
              {count ? (
                ...
                    {count}
                ...
              ) : (
                ...
                    <span className="label">ADD</span>
                ...
              )}
            </div>
          </div>
        );
      };
      
    4. In Cart.js you can import and consume the context to render and display the cart items.

      import { useContext } from "react";
      import { AppContext } from "../components/context/AppContext";
      
      const Cart = () => {
        const { cart, cartItems } = useContext(AppContext);
      
        return (
          <div>
            <h1> Cart Page </h1>
            <h2>Total Item Count: {cart}</h2>
            <p>
              <ul>
                {cartItems.map(({ id, name, quantity }) => (
                  <li key={id}>
                    {name}: {quantity}
                  </li>
                ))}
              </ul>
            </p>
          </div>
        );
      };
      

    Note: Please note that an id property was added to all items/products in your products array to make matching/identifying them a much easier task.

    Demo

    Edit Using Tailwind with Next.js (forked)