skip to Main Content

I creating clone of frontend of this website, everything is going smoothly but when I reached at cart section I find there is a lot of work. In short, I just create a Add to Cart button for my website and as user clicks the item placed in cart. In cart I want to create an edit functionality in which user can select his/her size of dress for this I create a modal (using Frammer-Motion) which will shown when user clicks add to cart button but every time when I click on Edit button, modal opens with the same data(which is the last item in cart) and when I click on sizeOption(i.e M, L, XL) it updates the same item (which is the last item)

Cart Component

import { BiEdit, BiMinusCircle, BiPlusCircle } from "react-icons/bi"
import { BsCartX } from "react-icons/bs"
import { MdDeleteOutline } from "react-icons/md"
import { Link } from "react-router-dom"
import { cartState } from "../recoilState"
import { useRecoilState } from "recoil"
import toast, { Toaster } from 'react-hot-toast';
import NewModal from '../../components/NewModal'
import { useState } from "react"
import { AnimatePresence} from "framer-motion"

const notify = () => toast.error('Item Removed From Cart')

const Cart = () => {

    const [cart, setCart] = useRecoilState(cartState)
    const [modalOpen, setModalOpen] = useState(false)
    
    const close = () => setModalOpen(false)
    const open = () => setModalOpen(true)
    
    const handleEdit = (selectedSize: any, id: string) => {
      setCart((prevCart) => {
        return prevCart.map((item) => {
          console.log('Checking Item', item.productId)
          if (item.productId === id) {
            console.log('updating Item', item.productId)
            return {
              ...item,
              selectedSize: selectedSize,
            }
          } else {
              return item
          }
        });
      });
    };
    
    
    const handleQuantityChange = (id: string, action: string) => {
      setCart((prevCart) => {
        return prevCart.map((item) => {
          if (item.productId === id) {
            if (action === 'increment') {
              return { ...item, quantityInCart: item.quantityInCart + 1 };
            } else if (action === 'decrement' && item.quantityInCart > 1) {
              return { ...item, quantityInCart: item.quantityInCart - 1 };
            }
          }
          return item;
        });
      });
    };
    
    const removeItem = (id: string) => {
      setCart((prevCart) => prevCart.filter((item) => item.productId !== id));
    };
    
    return (        
    
        <div className="p-40">
          
          {cart.length === 0 
          
          ? 

          (
            <div className='flex flex-col items-center gap-y-44 '>
              <h1 className='font-bold text-xl'>SHOPPING CART</h1>
    
              <div className="flex flex-col items-center w-[800px] text-left gap-y-10">
                <BsCartX size={150} />
                <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Pariatur similique voluptatem nisi fugit aperiam quasi veniam temporibus, magni fuga repudiandae.</p>
                <Link to={'/'}>
                    <button className="bg-black p-4 text-lg text-white rounded-lg">Return to Shopping</button>
                </Link>
              </div>
            </div>
          ) 
          
          : 
          (
            <>
              <h1 className="text-xl text-center font-semibold mb-10 uppercase">Your Cart</h1>
              
              {cart.map((item, index) => {
                return (
                  <div className="flex gap-x-5 mt-16" key={index}>
                  <div className="flex flex-col gap-y-10 items-start w-[50%]">
    
                    <h2 className="font-semibold uppercase ">
                      Product
                    </h2>
    
                    <div className="flex gap-x-5 items-center">
                      <img 
                      src={item.image}
                      className="rounded-lg w-[100px]"
                      />
                      <div className="flex flex-col justify-between">
    
                        <div className="text-sm flex flex-col gap-y-2">
                          <h4 className="font-semibold">{item.name}</h4>
                          <p className="">Category: {item.category}</p>
                          <p>Size: {item.selectedSize}</p>
                        </div>
    
                        <div className="flex gap-x-2 mt-5">
                          <BiEdit 
                            size={20} 
                            onClick={open} 
                            className="cursor-pointer"  
                          />
    
                          <MdDeleteOutline 
                            className="cursor-pointer"
                            size={20} 
                            onClick={() => {
                              removeItem(item.productId)
                              notify()
                            }} 
                            />
    
                            <Toaster />
                        </div>
    
                      </div>
                    </div>
                  </div>
    
                  <div className="flex flex-col w-[20%] gap-y-24">
                    <h2 className="font-semibold uppercase ">Price</h2>
                    <p>Rs. {item.price}</p>
                  </div>
    
                  <div className="flex flex-col items-center w-[20%] gap-y-24">
                    <h2 className="font-semibold uppercase ">Quantity</h2>
                    <div className="flex gap-x-3 items-center">
                      <BiPlusCircle size={20} onClick={() => handleQuantityChange(item.productId, 'increment')} />
                      {item.quantityInCart}
                      {item.quantityInCart > 1 && <BiMinusCircle size={20} onClick={() => handleQuantityChange(item.productId, 'decrement')} />}
                    </div>
                  </div>
    
                  <div className="flex flex-col items-end w-[20%] gap-y-24">
                    <h2 className="font-semibold uppercase ">Total</h2>
                    <p>Total Price: {item.quantityInCart * item.price}</p>
                  </div>
    
                  <AnimatePresence
                    initial={false}
                    mode='wait'
                    onExitComplete={() => null}
                  >
    
                    {modalOpen && <NewModal modalOpen={modalOpen} handleClose={close} text={item.name} size={item.sizeOptions} id={item.productId} handleEdit={handleEdit} infoModal={false} />}
                  </AnimatePresence>     
            
    
                </div>
                )
              })}
                  <div className="mt-20 text-xl font-semibold">
                    Total Bill: {cart.reduce((total: any, item: any) => total + item.quantityInCart * item.price, 0)}
                  </div>
            </>
          )
          }
            
            
    
        </div>
      
      )
    }
    
    export default Cart

Modal Component

import React from "react";
import { motion } from "framer-motion";
import Backdrop from "./Backdrop";

const dropIn = {
    hidden: {
      y: "-100vh",
      opacity: 0,
    },
    visible: {
      y: "0",
      opacity: 1,
      transition: {
        duration: 0.1,
        type: "spring",
        damping: 25,
        stiffness: 500,
      },
    },
    exit: {
      y: "100vh",
      opacity: 0,
    },
  };

const styles: React.CSSProperties = {
    width: 'clamp(50%, 700px, 90%)',
    borderRadius: '12px',
    backgroundColor: '#fff',
}
  

const Modal = ({ handleClose, text, infoModal, size, id, handleEdit }: any) => {


    return (
      <Backdrop onClick={handleClose}>
          <motion.div
            onClick={(e) => e.stopPropagation()}  
            style={styles}
            variants={dropIn}
            initial="hidden"
            animate="visible"
            exit="exit"
          >
            
            {infoModal === true ?  
                (
                  <div className="flex justify-between items-center p-10">
                      <p className="text-2xl">{text}</p>
                      <button onClick={handleClose} className=" bg-orange-600 px-4 py-2  rounded-full" >Close</button>
                  </div>
                ) 

                : 
                
                (
                  <div className="p-10 flex flex-col gap-y-5">
                    {text}
                    <div className="flex gap-x-5 items-center">     
                      <p>Size:</p> 
                      {size.map((selectedSize: any, index: any) => {
                        return (
                          <motion.div
                            key={index}
                            className="border cursor-pointer px-4 py-2 text-lg" 
                            onClick={() => handleEdit(selectedSize, id)} 
                            whileHover={{backgroundColor: "black", color: 'white'}}
                            whileTap={{backgroundColor: "#707B7C"}}
                            >
                              {selectedSize}
                            
                          </motion.div>
                        )
                        })}   
                          
                    </div>
                    <button onClick={handleClose} className=" bg-orange-600 px-4 py-2  rounded-full" >Close</button>
                  </div>
                )
            }
                
          </motion.div>
      </Backdrop>
    );
  };

  
  export default Modal;

I want to display data on the modal according to the input and update on cart accordingly

2

Answers


  1. You’re using a single boolean value to determine whether or not every modal is open. So when you try to open the modal for any record, you open all modals for every record.

    Instead of just tracking whether or not all modals should be open, track which modal should be open. For example, your modalOpen state value could be the productId of the record for which you want to open the modal. Something like this:

    const [modalOpen, setModalOpen] = useState();
    

    It’s initially undefined because initially no modal is open. Opening one would involve setting the value to that record’s identifier:

    onClick={() => setModalOpen(item.productId)}
    

    And closing it would involve setting it back to undefined:

    onClick={() => setModalOpen(undefined)}
    

    (You could also use something like -1 as a magic value that matches no records, it’s up to you.)

    Then for each record you’d check if the state value matches that record’s identifier to see if the modal should be open:

    {modalOpen === item.productId &&
      <NewModal
        handleClose={() => setModalOpen(undefined)}
        text={item.name}
        size={item.sizeOptions}
        id={item.productId}
        handleEdit={handleEdit}
        infoModal={false}
      />
    }
    
    Login or Signup to reply.
  2. your "modalOpen" bool toggles ALL your modals. i assume they are stacking and the last one is always on top.
    instead, perhaps you can set another value in state when calling "open" that tracks which item is being edited. then each modal could be displayed based on a bit of logic. something like this:

    {modalIsOpen && editingItemId == item.id && <NewModal></NewModal>}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search