I'm pretty new to React, and I'm making an ecommerce website for practice. I have a cart component that dynamically renders cart items from an API, but I narrowed the problem 100% down to the front end. On the initial render, it works fine, everything that is in the database cart appears, however after I press the "Delete from Cart" button on a cart item, each cart item doubles.
CartCard.jsx (/api/delete returns a json object of the cart AFTER deletion. deletion is handled in the api)
import React from "react";
import CartCardCSS from "./CartCard.module.css";
export default function CartCard(props) {
const passCart = props.passCart;
function deleteFromCart(e) {
e.preventDefault();
const item = {
id: props.id,
mainText: props.mainText,
price: props.price,
type: props.type
}
const user = {
username: props.username
}
const compoundEntity = {
theContent: item,
theUser: user
}
const requestOptions = {
method: "POST",
headers: {"Content-Type" : "application/json"},
body: JSON.stringify(compoundEntity)
}
fetch("/api/delete", requestOptions)
.then(response => response.json())
.then(response => {
passCart(response)
console.log(response)
})
}
return (
<div className={CartCardCSS.card}>
<img src={props.image} className={CartCardCSS.itempicture} alt="item picture"/>
<h3 className={CartCardCSS.subitem}>{props.mainText}</h3>
<h4 className={CartCardCSS.subitem}>{props.price}</h4>
<button onClick={deleteFromCart} className={`${CartCardCSS.subitem} ${CartCardCSS.button}`} type="button">Remove From Cart</button>
</div>
)
}
Cart.jsx:
import React, {useEffect, useState} from "react";
import CartCard from "./CartCard";
import CpuPicture from "./img/cpu.jpg";
import GpuPicture from "./img/gpu.jpg";
export default function Cart(props) {
const cart = props.cart;
const username = props.username;
const passCart = props.passCart;
const [arrCart, setArrCart] = useState(Object.values(cart))
const [cartComp, setCartComp] = useState()
useEffect(() => {
console.log(cart)
setArrCart(Object.values(cart))
for(let i = 0; i < arrCart.length; i ) {
setCartComp(arr => [arr, <CartCard key={i} id={i} passCart={passCart} username={username} mainText={arrCart[i].mainText} price={arrCart[i].price} type={arrCart[i].type} image={arrCart[i].type === "cpu" ? CpuPicture : GpuPicture}/>])
}
}, [cart])
return (
<div>
<h2 >Cart:</h2>
{cartComp}
</div>
)
}
Profile.jsx:
import React from "react";
import Cart from "./Cart";
import Navbar from "./Navbar";
import {useNavigate} from "react-router-dom";
export default function Profile(props) {
const username = props.username;
const isLoggedIn = props.isLoggedIn;
const cart = props.cart;
const passCart = props.passCart;
let navigate = useNavigate();
const routeChange = () => {
let path = "/login";
navigate(path);
}
if (isLoggedIn) {
return (
<div>
<Navbar />
<h1>{username}</h1>
<Cart passCart={passCart} username={username} cart={cart} />
</div>
)
} else {
routeChange();
}
}
App.jsx
import React, {useState} from "react";
import './App.css';
import Login from "./components/Login";
import Signup from "./components/Signup";
import Home from "./components/Home";
import Profile from "./components/Profile";
import Card from "./components/Card";
import GpuPicture from "./components/img/gpu.jpg";
import CpuPicture from "./components/img/cpu.jpg";
import {BrowserRouter, Routes, Route, Navigate} from "react-router-dom";
function App() {
const [username, setUsername] = useState('');
const [cart, setCart] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false)
function passUsername(items) {
setUsername(items);
}
function passCart(items) {
setCart(items);
}
function passIsLoggedIn(items) {
setIsLoggedIn(items);
}
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to="/login"/>} />
<Route path="/login" element={<Login passIsLoggedIn={passIsLoggedIn} passUsername={passUsername} passCart={passCart}/>}/>
<Route path="/signup" element={<Signup />}/>
<Route path="/home" element={<Home passCart={passCart} cart={cart} username={username} isLoggedIn={isLoggedIn} />} />
<Route path="/profile" element={<Profile passCart={passCart} cart={cart} username={username} isLoggedIn={isLoggedIn}/>} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
Thanks for your help
CodePudding user response:
This for
loop might be the issue:
useEffect(() => {
console.log(cart)
setArrCart(Object.values(cart))
for(let i = 0; i < arrCart.length; i ) {
setCartComp(arr => [arr, <CartCard key={i} id={i} passCart={passCart} username={username} mainText={arrCart[i].mainText} price={arrCart[i].price} type={arrCart[i].type} image={arrCart[i].type === "cpu" ? CpuPicture : GpuPicture}/>])
}
}, [cart])
Keep in mind that setState
in react is asynchronous. What it means:
console.log(arrCart) // previous state
setArrCart(Object.values(cart))
console.log(arrCart) // still previous state
Try changing your for
loop into that:
for(let i = 0; i < cart.length; i ) {
setCartComp(arr => [arr, <CartCard key={i} id={i} passCart={passCart} username={username} mainText={cart[i].mainText} price={cart[i].price} type={cart[i].type} image={cart[i].type === "cpu" ? CpuPicture : GpuPicture}/>])
}
CodePudding user response:
As @deaponn mentioned, useState
is asynchronous.
Your for loop in setCartComp
is called n times. Alternatively what about using javascript's map
function:
useEffect(() => {
console.log(cart);
// map transforms the array into an array of another type (in this case CartCard instances)
setCartComp(Object.values(cart).map((item, index) =>
<CartCard key={index} id={index}
passCart={passCart}
username={username}
mainText={item.mainText}
price={item.price}
type={item.type}
image={item.type === "cpu" ? CpuPicture : GpuPicture} />));
}, [cart]);