Home > Back-end >  When a button in React is clicked, the component renders twice
When a button in React is clicked, the component renders twice

Time:08-18

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]);
  • Related