Search code examples
javascriptreactjstypescriptmodal-dialogsimplemodal

Data in modal is not updating in React JS


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


Solution

  • 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>}