skip to Main Content

I am studying Front end development and am writing simple shopping cart in React to learn. When I change product quantity I want to create an object of quantity values by product id and pass them to shopping cart component to display later. The quantities data that I gather seems to be correct, because I can see them displaying correctly in QTY: state as shown in this picture:enter image description here

,but when I pass quantities state to the parent and then from a parent to a shopping cart component and console.log it – the data seems to be all over the place… Sometimes it is not picked up at all, sometimes too small by 1 and so on…

I’ve seen some videos where people build shopping cart and they seem to use state management tools like react useContext or Redux and I get it global state would be much nicer than passing data from child to parent, then from parent to another child, but I think I am not yet ready for state management tools.

App component:

import { useState } from "react";
import ProductCard from "./components/ProductCard";
import ShoppingCart from "./components/ShoppingCart";

function App() {
  let [productData, setProductData] = useState({});

  function getProductData(cartData: object) {
    setProductData(cartData);
  }

  return (
    <>
      <div>
        <ProductCard passProductData={getProductData} />
        <ShoppingCart productData={productData} />
      </div>
    </>
  );
}

export default App;

Cart Component:

interface Props {
  productData: object;
}

function ShoppingCart({ productData }: Props) {
  return (
    <>
      <button
        onClick={() => {
          console.log(JSON.stringify(productData));
        }}
        className="bg-slate-400"
      >
        Test passed data
      </button>
      <h1 className="font-bold">Your cart</h1>
      <p className="font-bold">Order Total</p>
    </>
  );
}

export default ShoppingCart;

Product card component:

import productList from "../data/productList";
import { useState } from "react";

interface Props {
  passProductData: (quantities: object) => void;
}

function ProductCard({ passProductData }: Props) {
  let [newProductList, changeProductList] = useState(productList);
  let [productQuantities, setProductQuantities] = useState({
    0: 0,
    1: 0,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
  });
  function revealQuantityInput(id: number) {
    changeProductList(
      newProductList.map((product) => {
        if (product.id === id) {
          return { ...product, quantityVisibility: true };
        } else return product;
      })
    );
  }

  function increaseQuantity(id: number) {
    for (let [key, value] of Object.entries(productQuantities)) {
      if (Number(key) === id) {
        setProductQuantities({
          ...productQuantities,
          [key]: value + 1,
        });
      }
    }
    passProductData(productQuantities);
  }

  function decreaseQuantity(id: number) {
    for (let [key, value] of Object.entries(productQuantities)) {
      if (Number(key) === id) {
        setProductQuantities({
          ...productQuantities,
          [key]: value > 0 ? value - 1 : value,
        });
      }
    }
    passProductData(productQuantities);
  }

  function setSpecificQuantityValue(id: number, quantity: number) {
    newProductList.map((product) => {
      if (product.id === id) {
        return setProductQuantities({
          ...productQuantities,
          [product.id]: quantity,
        });
      }
    });
    passProductData(productQuantities);
  }

  return (
    <>
      {newProductList.map((product) => (
        <div className="p-6 mb-6 [&>*]:mb-4" key={product.id}>
          <img src={product.image} />
          <div
            className="bg-green-600 p-2"
            onClick={() => revealQuantityInput(product.id)}
          >
            {product.quantityVisibility ? (
              <div>
                <p onClick={() => decreaseQuantity(product.id)}>-</p>
                <input
                  type="number"
                  id={"quantity" + product.id}
                  min="1"
                  max="9"
                  step="1"
                  onChange={(e) =>
                    setSpecificQuantityValue(product.id, Number(e.target.value))
                  }
                  className="bg-gray-300 w-[150px] opacity-100 mr-3"
                />
                <p onClick={() => increaseQuantity(product.id)}>+</p>
              </div>
            ) : (
              <p>Add to Cart</p>
            )}
          </div>
          <p>{product.category}</p>
          <p>{product.name}</p>
          <p>{product.price}</p>
          <p>QTY: {JSON.stringify(productQuantities)}</p>
        </div>
      ))}
    </>
  );
}

export default ProductCard;

Product data:

import productTypes from "../types/productTypes"

const productList:productTypes[] = [
    {
        id: 0,
        image: "../../images/image-waffle-mobile.jpg",
        name: "Waffle with Berries",
        category: "Waffle",
        price: 6.50,
        quantityVisibility: false,
    },
    {
        id: 1,
        image: "../../images/image-creme-brulee-mobile.jpg",
        name: "Vanilla Bean Crème Brûlée",
        category: "Crème Brûlée",
        price: 7.00,
        quantityVisibility: false,
    },
    {
        id: 2,
        image: "../../images/image-macaron-mobile.jpg",
        name: "Macaron Mix of Five",
        category: "Macaron",
        price: 8.00,
        quantityVisibility: false,
    },
    {
        id: 3,
        image: "../../images/image-tiramisu-mobile.jpg",
        name: "Classic Tiramisu",
        category: "Tiramisu",
        price: 5.50,
        quantityVisibility: false,
    },
    {
        id: 4,
        image: "../../images/image-baklava-mobile.jpg",
        name: "Pistachio Baklava",
        category: "Baklava",
        price: 4.00,
        quantityVisibility: false,
    },
    {
        id: 5,
        image: "../../images/image-meringue-mobile.jpg",
        name: "Pistachio Baklava",
        category: "Baklava",
        price: 4.00,
        quantityVisibility: false,
    },

]

export default productList

Thank you in advance!

Regards,
Darius

2

Answers


  1. I would check to make sure that this is getting up to your App first by console.log ing it. This can be a little sanity check for you but at the moment I don’t believe it is getting up there.
    Firstly I would fully get rid of the getProductData function and simply pass the function provided by useState, the setProductData.
    After that I would update the way you are actually using this function. Instead of what you currently have to update it every time the quantities are being updated by the onclick functions I would remove it from everything and opt to use an useEffect.

    useEffect(()=>{
     setProductData(productQuantities)
    }, [productQuantities]);
    

    I would make sure everything is still being set properly here with these changes and then make sure its being passed up to your App component.
    Let me know if you run into any problems, and Redux isn’t too bad once you get into it and you are still learning anyway so I would encourage using that over this 🙂

    Login or Signup to reply.
  2. Issues

    You have a few anti-patterns and various issues in your code.

    1. The productData state from App is effectively duplicated in Productcard and there exists state synchronicity issues. This is why you see the mis-matched states in the console log.
    2. ProductCard is enqueueing state updates to the parent component immediately after enqueueing a productQuantities state update. React state updates are not immediately processed, so the old state is passed to the parent. This further leads to quantity discrepancies in the console logs.

    Solution Suggestion

    The productData state in App should be the single source of truth.

    • Update App to pass down both productData and setProductData to the ProductCard component. Use a lazy state initializer function to initialize the state with the initial 0 quantities by product id.

      import { useState } from "react";
      import ProductCard from "./components/ProductCard";
      import ShoppingCart from "./components/ShoppingCart";
      import productList from "./data/productList";
      
      export default function App() {
        const [productData, setProductData] = useState<Record<string, number>>(() =>
          productList.reduce(
            (quantities, { id }) => ({
              ...quantities,
              [id]: 0,
            }),
            {}
          )
        );
      
        return (
          <div>
            <ProductCard setProductData={setProductData} productData={productData} />
            <ShoppingCart productData={productData} />
          </div>
        );
      
    • Update ProductCard component to enqueue state updates directly to the parent state. Use functional state updates to correctly update from the previous state value.

      import productListData from "../data/productList";
      import { useState } from "react";
      
      interface Props {
        setProductData: React.Dispatch<React.SetStateAction<Record<string, number>>>;
        productData: Record<string, number>;
      }
      
      function ProductCard({ setProductData, productData }: Props) {
        const [productList, changeProductList] = useState(productListData);
      
        function revealQuantityInput(id: number) {
          changeProductList((productList) =>
            productList.map((product) =>
              product.id === id
                ? { ...product, quantityVisibility: true }
                : product
            )
          );
        }
      
        function increaseQuantity(id: number) {
          setProductData((quantities) => ({
            ...quantities,
            [id]: quantities[id] + 1,
          }));
        }
      
        function decreaseQuantity(id: number) {
          setProductData((quantities) => ({
            ...quantities,
            [id]: quantities[id] - 1,
          }));
        }
      
        function setSpecificQuantityValue(id: number, quantity: number) {
          setProductData((quantities) => ({
            ...quantities,
            [id]: quantity,
          }));
        }
      
        return productList.map((product) => (
          <div className="p-6 mb-6 [&>*]:mb-4" key={product.id}>
            <img src={product.image} />
            <div
              className="bg-green-600 p-2"
              onClick={() => revealQuantityInput(product.id)}
            >
              {product.quantityVisibility ? (
                <div>
                  <p onClick={() => decreaseQuantity(product.id)}>-</p>
                  <input
                    type="number"
                    id={"quantity" + product.id}
                    min="1"
                    max="9"
                    step="1"
                    onChange={(e) =>
                      setSpecificQuantityValue(product.id, Number(e.target.value))
                    }
                    className="bg-gray-300 w-[150px] opacity-100 mr-3"
                  />
                  <p onClick={() => increaseQuantity(product.id)}>+</p>
                </div>
              ) : (
                <p>Add to Cart</p>
              )}
            </div>
            <p>{product.category}</p>
            <p>{product.name}</p>
            <p>{product.price}</p>
            <p>QTY: {JSON.stringify(productData)}</p>
          </div>
        ));
      }
      
      export default ProductCard;
      
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search