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:
,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
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.
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 🙂
Issues
You have a few anti-patterns and various issues in your code.
productData
state fromApp
is effectively duplicated inProductcard
and there exists state synchronicity issues. This is why you see the mis-matched states in the console log.ProductCard
is enqueueing state updates to the parent component immediately after enqueueing aproductQuantities
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 inApp
should be the single source of truth.Update
App
to pass down bothproductData
andsetProductData
to theProductCard
component. Use a lazy state initializer function to initialize the state with the initial 0 quantities by product id.Update
ProductCard
component to enqueue state updates directly to the parent state. Use functional state updates to correctly update from the previous state value.