Search code examples
reactjslocal-storageapolloapollo-clientshopping-cart

Best Way to Save or Store Cart Items by each user? (Best Practice or Way) [MERNG STACK]


I'm trying to make an ecommerce for my personal project and I'm trying to use everything I've learned to do so. I currently have this code that allows you to store cartItems in a local Storage, but I believe it's incorrect what I did because in this scenario, for example, user 1 added his items to the cart or products to the cart, and then user 2 can access or have the same cart when I login with them. Which is the best practice or choice for the user to save cartItems? Or, rather, what should I do?

Currently the technology I use the M(ongoDB)E(xpress)R(eact)N(odejs)G(raphql) and Apollo for my state management.

Code


CartContext:

import { createContext, useState, useContext, useEffect } from "react";
import { AuthContext } from "../auth";

export const CartContext = createContext();

export const CartProvider = ({ children }) => {
  const { user } = useContext(AuthContext);
  const cartFromLocalStorage =
    (typeof window !== "undefined" &&
      JSON.parse(localStorage.getItem("cart"))) ||
    [];

  const [cartItems, setCartItem] = useState(cartFromLocalStorage);

  useEffect(() => {
    if (typeof window !== "undefined") {
      if (user) { // This just shows if the user is loggedIn
        localStorage.setItem("cart", JSON.stringify(cartItems));
      }
    }
  }, [cartItems]);

  return (
    <CartContext.Provider value={[cartItems, setCartItem]}>
      {children}
    </CartContext.Provider>
  );
};

This is my CartContext, where I can see what the user has added to their cart and call it whenever I want because it's a context that doesn't require to pass props to a component.

Shop:

import { useContext } from "react";
import { useQuery } from "@apollo/react-hooks";
import { FETCH_PRODUCTS_QUERY } from "../util/graphql/Queries";

import { Grid, Transition } from "semantic-ui-react";
import ProductCard from "../components/products/ProductCard";
import { CartContext } from "../context/cart/CartContext";

function Shop() {
  const { loading, data: Products } = useQuery(FETCH_PRODUCTS_QUERY);

  const { getProducts: products } = { ...Products };

  const [cartItems, setCartItems] = useContext(CartContext);

  const addToCart = (product) => {
    const exist = cartItems.find((x) => x.id === product.id);
    if (exist) {
      setCartItems(
        cartItems.map((x) =>
          x.id == product.id ? { ...exist, quantity: exist.quantity + 1 } : x
        )
      );
    } else {
      setCartItems([...cartItems, { ...product, quantity: 1 }]);
    }
  };

  const ProductList = products?.map((product) => (
    <Grid.Column key={product.id} style={{ marginBottom: 20 }}>
      <ProductCard
        product={product}
        addToCart={(product) => addToCart(product)}
      />
    </Grid.Column>
  ));

  return (
    <>
      <Grid columns={3}>
        <Grid.Row className="page-title">
          <h1>Recent Products</h1>
        </Grid.Row>
        <Grid.Row>
          {loading ? (
            <h1>Loading products..</h1>
          ) : (
            <Transition.Group>{ProductList}</Transition.Group>
          )}
        </Grid.Row>
      </Grid>
    </>
  );
}

export default Shop;

This is my Shop or where the Product is located as shown I passed the addToCart function to the ProductCard Component.

ProductCard:

 import { Image, Card } from "semantic-ui-react";
    import Link from "next/link";
    import Rating from "../reviews/Rating";
    
    function ProductCard({ product, addToCart }) {
      const {
        id,
        name,
        featureImage,
        productCurrentPrice,
        productPreviousPrice,
        rating,
        numReviews,
      } = product;
    
      return (
        <>
          <Card>
            <Image
              src={featureImage}
              width={200}
              height={200}
              alt="Product Image"
              className="product-image"
            />
            <Card.Content>
              <Card.Header>
                <Link href={`/products/${id}`}>{name}</Link>
              </Card.Header>
              <Card.Meta>
                <span className="date">
                  ₱{productCurrentPrice.toLocaleString()}
                </span>
                <span className="date">
                  <strike>₱{productPreviousPrice.toLocaleString()}</strike>
                </span>
                &nbsp;
                <span className="header">{percentOff} % OFF</span>
              </Card.Meta>
            </Card.Content>
            <Card.Content extra>
              <div className="ui two buttons">
                <button
                  className="ui basic green button"
                  onClick={() => addToCart(product)}
                >
                  Add To Cart
                </button>
              </div>
            </Card.Content>
          </Card>
        </>
      );
    }
    
    export default ProductCard;

This is my ProductCard Component which when addToCart it will go to the localStorage just like this:

enter image description here

As shown above, the image the cart key is where all the products I added is inputted there. even when I logout the cartItem is still shown there, which it should not.

If you need anymore clarification or explanation, I can provide it, if code I will also provide I will be transparent as I can.


Solution

  • Since each user has their own unique cart available, you can save the cart info the along with the userId/username of the current user as identifier. So when a user logs in you can just fetch the their cart using that identifier of the user

    usually for storing such sensitive data it is better to save them directly in your DB rather than the local storage.

    You can create an async Api call such that whenever you add items to cart, your corresponding data in table is also updated. Example a "Cart" table containing the current cart details of all users