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
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 theproductId
of the record for which you want to open the modal. Something like this:It’s initially
undefined
because initially no modal is open. Opening one would involve setting the value to that record’s identifier:And closing it would involve setting it back to
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:
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: