React add to cart

Hi guys, I have another questions (though this one I can’t figure out on my own). I am working on a e-commerce mockup app, I have a VinylClocks component which, along with Product component, renders all the products I have, from my Data.js object array. The ProductPage component renders the details about the specific clock user clicked on previously, but I can’t seem to figure out how to implement add-to-cart logic. Here is the code:

import React, { useEffect, useMemo, useState } from "react";
import "./VinylClocks.css";
import Navbar from "./Navbar.jsx";
import Footer from "./Footer.jsx";
import SortButton from "./SortButton.jsx";
import { data } from "./data.js";
import Product from "./Product.jsx";
import FilterButton from "./FilterButton.jsx";
import Pagination from "./Pagination.jsx"; 

export default function VinylClocks() {
  const [dataList, setDataList] = useState([]);
  const [selectedCategory, setSelectedCategory] = useState("");
  const [sortOption, setSortOption] = useState("latest");
  const [currentPage, setCurrentPage] = useState(1);
  const [itemsPerPage] = useState(12);

  // Add default value on page load
  useEffect(() => {
    setDataList(data);
  }, []);

  // Function to get filtered and sorted list
  function getFilteredAndSortedList() {
    let filteredList = dataList;
    // Filter by category
    if (selectedCategory) {
      filteredList = dataList.filter((item) => item.cat === selectedCategory);
    }

    // Sort by 
    switch (sortOption) {
      case "highToLow":
        filteredList.sort((a, b) => b.price - a.price);
        break;
      case "lowToHigh":
        filteredList.sort((a, b) => a.price - b.price);
        break;
        case "oldest":
          filteredList.sort((a, b) => a.id - b.id);
        break;

      default:
        // Latest or default sorting
        case "latest":
        filteredList.sort((a, b) => b.id - a.id);
        break;
    }

    return filteredList;
  }

  // Avoid duplicate function calls with useMemo
  var filteredAndSortedList = useMemo(getFilteredAndSortedList, [
    selectedCategory,
    sortOption,
    dataList,
  ]);

  function handleCategoryChange(option) {
    setSelectedCategory(option);
  }

  function handleSortChange(option) {
    setSortOption(option);
  }
  const indexOfLastItem = currentPage * itemsPerPage;
  const indexOfFirstItem = indexOfLastItem - itemsPerPage;
  const currentItems = filteredAndSortedList.slice(indexOfFirstItem, indexOfLastItem);

  const paginate = pageNumber => setCurrentPage(pageNumber);

  return (
    <>
      <header className="header">
        <Navbar />
      </header>
      <div className="vc-container">
        <div className="filter-sort">
          <FilterButton onFilterChange={handleCategoryChange}/>
          <SortButton onSortChange={handleSortChange} />
        </div>
        <div className="products-container">
          <div className="catSort-container">
              <form onFilterChange={handleCategoryChange} className="cat-form" action="#">
                <h5>Categories:</h5>
                <input type="checkbox" checked={selectedCategory === ""} onChange={() => setSelectedCategory("")} id="all" className="radio-input" name="" value=""></input>
                <label htmlFor="all">All</label><br/>
                <input type="checkbox" checked={selectedCategory === "movies"} onChange={() => setSelectedCategory( "movies")} id="movies" className="radio-input" name="" value="movies"></input>
                <label htmlFor="movies">Movies</label><br/>
                <input type="checkbox" checked={selectedCategory === "music"} onChange={() => setSelectedCategory("music")} id="music" className="radio-input" name="" value="music"></input>
                <label htmlFor="music">Music</label><br/>
                <input type="checkbox" checked={selectedCategory === "sport"} onChange={() => setSelectedCategory("sport")} id="sport" className="radio-input" name="" value="sport"></input>
                <label htmlFor="sport">Sport</label><br/>
                <input type="checkbox" checked={selectedCategory === "other"} onChange={() => setSelectedCategory("other")} id="other" className="radio-input" name="" value="other"></input>
                <label htmlFor="other">Other</label>
            </form>
            <form onSortChange={handleSortChange} className="sort-form" action="#">
                <h5>Sort by:</h5>
                <h3>Date:</h3>
                <input type="checkbox" checked={sortOption === "latest"} onChange={() => setSortOption("latest")}  
                       id="latest" className="radio-input" name="" value="latest"></input>
                <label>Latest</label><br/>
                <input type="checkbox" checked={sortOption === "oldest"} onChange={() => setSortOption( "oldest")} 
                       id="oldest" className="radio-input" name="" value="oldest"></input>
                <label>Oldest</label><br/>
                <h3>Price:</h3>
                <input type="checkbox" checked={sortOption === "highToLow"} onChange={() => setSortOption("highToLow")} 
                       id="lowest" className="radio-input" name="" value="highToLow"></input>
                <label>Highest</label><br/>
                <input type="checkbox" checked={sortOption === "lowToHigh"} onChange={() => setSortOption( "lowToHigh")} 
                       id="highest" className="radio-input" name="" value="lowToHigh"></input>
                <label>Lowest</label>
                
            </form>
          </div>
            <div className="products">
             <Product filteredAndSortedList={currentItems}/>
              </div>
        </div>
        <Pagination
        itemsPerPage={itemsPerPage}
        totalItems={filteredAndSortedList.length}
        paginate={paginate}
      />
      </div>
      <Footer />
    </>
  );
}
  
import React from "react";
import "./VinylClocks.css";
import { Link } from "react-router-dom";

export default function Product({filteredAndSortedList}) {

    return (
        <>
           {filteredAndSortedList.map(item => (
            <div className="products-card" key={item.id}>
                <Link to={`/product/productpage/${item.name}`}><img src={item.img}/></Link>
                <div className="name-price-tag">
                    <div className="price-container">
                        <p className="name">{item.name}</p>
                        <span className="price">${item.price}</span>
                    </div>
            </div>   
        </div>
           ))} 
                
        </>
    )        
}
import React from "react";
import "./ProductPage.css";
import Navbar from "./Navbar.jsx";
import Footer from "./Footer.jsx";
import { data } from "./data.js";
import { useParams } from 'react-router-dom';
 

export default function ProductPage() {
    const { name } = useParams();

  const item = data.find(item => item.name === name);

  if (!item) {
    return <div>No item</div>;
  }
    return (
      <>
        <Navbar/>
        <div className="productPage-container" key={item.id}>
            <div className="image-container">
                <img src={item.img}/>
            </div>
            <div className="about-product">
                <h2 className="product-name">{item.name}</h2>
                <span className="product-type">{item.type}</span>
                <span className="product-price">${item.price}</span>
                <p className="product-desc">{item.desc}</p>
                <button className="addToCart"> Add to Cart</button>
            </div>
        </div>
        <Footer/>
      </>  
      );
}
import React, {useState} from 'react'

export default function Cart() {
    const [cartItems, setCartItems] = useState([]);
  return (
    <div>
      {cartItems.length === 0 && <div>Cart is empty</div>}
    </div>
  )
}

p.s. so far, the Cart component only dispaly message if nothing is in the cart.
Thanks in advance!

Your cart component has only conditional content for an empty cart:

Add something you want to display if your cart is not empty, like:

<div>
      {cartItems.length === 0 ? (<div>Cart is empty</div>) : cartItems.map((item, index) => (<div>{item.name}</div>))}
    </div>

I understand that. I only knew how to implement that part (sadly).
What I’m trying to do is create the logic so when the Add to cart button in ProductPage component is clicked, it adds that specific product to the cart.
I did find one possible solution, but couldn’t adapt it to my code since it is fundamentally different from the way my app renders the products, but also because I’m still learning. And I also tried using useParams, like I did with ProductPage, but again, didn’t work cause I can’t seem to wrap my head around it yet.

This needs an onClick event handler (I’m doing it the simple way without diving deeper into any extra libraries as this is a standard task):

<button className="addToCart" onClick={() => handleClick(item.id)}> Add to Cart</button>

Write the function handleClick in the ProductPage component. It needs to accept an argument for the item id. And it needs to set the state for the items. Move the cartItems state to this component and then pass the state as props to the Cart component.
use a callback function for calling the state setter and add the new item to the previous items. But filter the items first in order to see if the items was already added.
I would recommend you do the Jammming project. It uses such a logic.

1 Like

Thanks. Funny thing is, I got stuck on the Jamming project once hooks were introduced, and that was the main reason why I started a random project. lol I needed to try and practice on my own.

  1. Add the onClick event handler in the ProductPage as described above (pass the while item as an argument, not just the id).
  2. Write the handleClick function in that component. The function accepts one argument: the item
  3. Inside the function, call the state setter that needs to be moved to the ProductPage component.
  4. Call the state setter with a callback function:
setCartItems(prev => [...prev, item])
1 Like

Thanks for the help!

1 Like

I really don’t want to bother you more than I already did, but passing cartItems as props to Cart component doesn’t work. No errors in the ProductPage component, but Cart is full of them, starting with the .length undefined (my guess is because cartItems doesn’t represent anything in my code). I honestly don’t know how I managed to build everything so far. So frustrating
Again, sorry for bothering

Can you post your updated ProductPage component?

I tried some other solutions I found as well, but still didn’t work.

import React, {useState} from "react";
import "./ProductPage.css";
import Navbar from "./Navbar.jsx";
import Footer from "./Footer.jsx";
import { data } from "./data.js";
import { useParams } from 'react-router-dom';
 

export default function ProductPage() {
  const [cartItems, setCartItems] = useState([]);
  
    const { name } = useParams();

    const item = data.find(item => item.name === name);

  if (!item) {
    return <div>No item</div>;
  }
  function handleClick(item) {
    setCartItems(prev => [...prev, item]);
  }
    return (
      <>
        <Navbar/>
        <div className="productPage-container" key={item.id}>
            <div className="image-container">
                <img src={item.img}/>
            </div>
            <div className="about-product">
                <h2 className="product-name">{item.name}</h2>
                <span className="product-type">{item.type}</span>
                <span className="product-price">${item.price}</span>
                <p className="product-desc">{item.desc}</p>
                <button onClick={() => handleClick(item.id)} className="addToCart"> Add to Cart</button>
            </div>
        </div>
        <Footer/>
      </>  
      );
}

Try to follow each argument in each step. Log the arguments you’re passing and see if they are what you expect.

You’re passing the id here:

But expecting the whole item here:

What do you expect the cartItems state to look like? Could you write a mock state?