Search code examples
reactjsarrayse-commerce

Need guidance on how to resolve unique key prop error


I am working on an e commerce store and I have set up a product page with unique id's and am looping through the items to display them but on the check out page when I try to remove an item I suddenly end up with a nan value. This is the page where I am pulling the items from as a test.

This is the product page that populates the items in the cart, but I am getting a unique key error when I remove an item from the cart if there are 2 or more items in the cart. below is the cart page that shows items in the cart

I have given the key the unique identifier of product but now I am getting a new error of "Warning: Each child in a list should have a unique "key" prop. which results in a nan in the cart when I remove an item. " I am a bit stuck here.

import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
import products from './product'
import { useContext } from 'react';
import { useState, useEffect} from 'react';


import cart from '../context/cart'
import { CartContext } from '../context/cart';
const style = {
  position: 'absolute',
  top: '50%',
  left: '50%',
  transform: 'translate(-50%, -50%)',
  width: "100%",
  bgcolor: 'background.paper',
  border: '2px solid #000',
  boxShadow: 24,
  p: 4,
};
export default function BasicModal() {
  const [open, setOpen] = React.useState(false);
  const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);


  const { cartItems, addToCart, removeFromCart, clearCart, getCartTotal } = useContext(CartContext)
  useEffect(() => {
    // storing input name
    localStorage.getItem("cartItems", JSON.stringify(cartItems));
  }, [cartItems])


// payment is processed here

  return (
    <div>
      <Button onClick={handleOpen}   >

        ({cartItems.length})
        Check Out</Button>
      <Modal
      disableScrollLock={true}
      fullScreen={true}
        open={open}
        onClose={handleClose}
        aria-labelledby="modal-modal-title"
        aria-describedby="modal-modal-description"
      >
        <Box sx={style}>
          <>
            <div className="flex-col flex items-center bg-white gap-8 p-10 text-black text-sm">
              <h1 className="text-2xl font-bold">Cart</h1>
              <div className="flex flex-col gap-4">
  
  
                {cartItems.map((item) => (
                  <div className="flex justify-between items-center" key={products.id}>
                    <div className="flex gap-4">
                      <img src={item.imageId} key="2014" alt={item.title} className="rounded-md h-24" />
                      <div className="flex flex-col">
                        <h1 className="text-lg font-bold" key="2015">{item.title}</h1>
                        <p className="text-gray-600" key="2013">{item.price}</p>
                        <p className="text-gray-600" key="202">{item.description}</p>

                      </div>
                    </div>
                    <div className="flex gap-4">
                      <button
                        className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                        onClick={() => {
                          addToCart(item)
                        }}
                      >
                        +
                      </button>
                      <p>{item.quantity}</p>
                      <button
                        className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                        onClick={() => {
                          removeFromCart(item)
                        }}
                      >
                        -
                      </button>

                    </div>
                  </div>
                ))}
              </div>
              {
                cartItems.length > 0 ? (
                  <div className="flex flex-col justify-between items-center">
                    <h1 className="text-lg font-bold">Total: ${getCartTotal()}</h1>
                    <button
                      className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                      onClick={() => {
                        clearCart()
                      }}
                    >
                      Clear cart
                    </button>
{/* check out here */}
        
                    {/* <Button variant="primary" onClick={} >Check out
                      </Button>  */}
                  </div>

                ) : (
                  <>
                    <h1 className="text-lg font-bold">Your cart is empty</h1>

                  </>
                )
              }
            </div>
            <div class="col d-flex justify-content-center">
 </div>
          </>
        </Box>
      </Modal>
    </div>
  );
} 

this is where my context is

import { createContext, useState, useEffect } from 'react'
import  React from 'react'
import products from '../components/product';
export const CartContext = createContext()

export const CartProvider = ({ children }) => {
  const [cartItems, setCartItems] = useState(localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : [0])

  const addToCart = (item) => {
    const isItemInCart = cartItems.find((cartItem) => cartItem.id === item.id);

    if (isItemInCart) {
      setCartItems(
        cartItems.map((cartItem) =>
          cartItem.id === item.id
            ? { ...cartItem, quantity: cartItem.quantity + 1 }
            : cartItem
        )
      );
    } else {
      setCartItems([...cartItems, { ...item, quantity: 1 }]);
    }
  };

  const removeFromCart = (item) => {
    const isItemInCart = cartItems.find((cartItem) => cartItem.id === item.id);

    if (isItemInCart && isItemInCart.quantity === 1) {

      setCartItems(cartItems.filter((cartItem) => cartItem.id !== item.id));
    } else {
      setCartItems(
        cartItems.map((cartItem) =>
          cartItem.id === item.id
            ? { ...cartItem, quantity: cartItem.quantity - 1 }
            : 0,
          )
      );
    }
  };

  const clearCart = () => {
    setCartItems([]);
  };

  const getCartTotal = () => { 
    // cart item total is being added here
    return cartItems.reduce((total, cartItem) => total + cartItem.price * cartItem.quantity, 0);
  };

  useEffect(() => {
    localStorage.setItem("cartItems", JSON.stringify(cartItems));
  }, [cartItems]);

  useEffect(() => {
    const cartItems = localStorage.getItem("cartItems");
    if (cartItems) {
      setCartItems(JSON.parse(cartItems));
    }
  }, []);

  const initialValue = 0;
  const total = cartItems.reduce((accumulator,current) => accumulator + current.price * current.quantity, initialValue)


  return (
    <CartContext.Provider
      value={{
        cartItems,
        addToCart,
        removeFromCart,
        clearCart,
        getCartTotal,
      }}
    >
      {children}
    </CartContext.Provider>
  );
};

How can I resolve this.

I am getting is a nan error whenever I remove an item from a cart if there are more than 2 items in the cart. Unsure of how to resolve this.

          {cartItems.map((item) => (
                  <div className="flex justify-between items-center" key={products.id}>
                    <div className="flex gap-4">
                      <img src={item.imageId} key="2014" alt={item.title} className="rounded-md h-24" />
                      <div className="flex flex-col">
                        <h1 className="text-lg font-bold" key="2015">{item.title}</h1>
                        <p className="text-gray-600" key="2013">{item.price}</p>
                        <p className="text-gray-600" key="202">{item.description}</p>

                      </div>

as of right now this is my current error index.js:1210 Warning: Each child in a list should have a unique "key" prop.

Check the render method of BasicModal.


Solution

  • I was able to finally resolve this issue by adding the a unique key to each item in the map.

    Here is the context for the cart.

    import { createContext, useState, useEffect } from 'react'
    import React from 'react';
    export const CartContext = createContext()
    
    export const CartProvider = ({ children }) => {
      const [cartItems, setCartItems] = useState(localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : [])
    
      const addToCart = (item) => {
        const isItemInCart = cartItems.find((cartItem) => cartItem.id === item.id);
    
        if (isItemInCart) {
          setCartItems(
            cartItems.map((cartItem) =>
              cartItem.id === item.id
                ? { ...cartItem, quantity: cartItem.quantity + 1 }
                : cartItem
            )
          );
        } else {
          setCartItems([...cartItems, { ...item, quantity: 1 }]);
        }
      };
    
      const removeFromCart = (item) => {
        const isItemInCart = cartItems.find((cartItem) => cartItem.id === item.id);
    
        if (isItemInCart.quantity === 1) {
          setCartItems(cartItems.filter((cartItem) => cartItem.id !== item.id));
        } else {
          setCartItems(
            cartItems.map((cartItem) =>
              cartItem.id === item.id
                ? { ...cartItem, quantity: cartItem.quantity - 1 }
                : cartItem
            )
          );
        }
      };
    
      const clearCart = () => {
        setCartItems([]);
      };
    
      const getCartTotal = () => {
        return cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
      };
    
      useEffect(() => {
        localStorage.setItem("cartItems", JSON.stringify(cartItems));
      }, [cartItems]);
    
      useEffect(() => {
        const cartItems = localStorage.getItem("cartItems");
        if (cartItems) {
          setCartItems(JSON.parse(cartItems));
        }
      }, []);
    
      return (
        <CartContext.Provider
          value={{
            cartItems,
            addToCart,
            removeFromCart,
            clearCart,
            getCartTotal,
          }}
        >
          {children}
        </CartContext.Provider>
      );
    };
    

    and here is the updated code for the check out cart

    import * as React from 'react';
    import Box from '@mui/material/Box';
    import Typography from '@mui/material/Typography';
    import axios from 'axios';
    import Button from '@mui/material/Button';
    import Dialog from '@mui/material/Dialog';
    import DialogActions from '@mui/material/DialogActions';
    import DialogContent from '@mui/material/DialogContent';
    import DialogContentText from '@mui/material/DialogContentText';
    import DialogTitle from '@mui/material/DialogTitle';
    import { CartContext } from '../context/cart';
    import Axios from 'axios';
    import { useContext } from 'react';
    import { useState, useEffect} from 'react';
    export default function AlertDialog() {
      const [open, setOpen] = React.useState(false);
    
      const { cartItems, addToCart, removeFromCart, clearCart, getCartTotal } = useContext(CartContext)
      useEffect(() => {
        // storing input name
        localStorage.getItem("cartItems", JSON.stringify(cartItems));
      }, [cartItems])
    
    
      const handleCheckout = () => {
        
        
        ;
      };
    
      const handleClickOpen = () => {
        setOpen(true);
      };
    
      const handleClose = () => {
        setOpen(false);
      };
      const style = {
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        width: "100%",
        bgcolor: 'background.paper',
        border: '2px solid #000',
        boxShadow: 24,
        p: 4,
      };
      return (
        <React.Fragment>
          <Button variant="outlined" onClick={handleClickOpen}>
            Confirm check out here
          </Button>
          <Dialog
            open={open}
            disableScrollLock={false}
            fullScreen={true}
            onClose={handleClose}
            aria-labelledby="alert-dialog-title"
            aria-describedby="alert-dialog-description"
          >
            <DialogTitle id="alert-dialog-title">
              {"Cofirm check out here?"}
            </DialogTitle>
            <DialogContent>
             
            <Box sx={style}>
              <>
                <div className="flex-col flex items-center bg-white gap-8 p-10 text-black text-sm">
                  <h1 className="text-2xl font-bold">Cart</h1>
                  <div className="flex flex-col gap-4">
                  
                    {cartItems.map((item) => (
                      <div className="flex justify-between items-center" key={item.id} id={item.id}>
                        <div  className="flex gap-4">
                          <img  src={item.imageId} key={item.imageId  } alt={item.title} className="rounded-md h-24" />
                          <div className="flex flex-col">
                            <h1 className="text-lg font-bold" key={item.title  } >{item.title} </h1>
                            <p  className="text-gray-600" key={item.price  } > {item.price}</p>
                            <p  className="text-gray-600"key={item.description  }>{item.description} </p>
    
                          </div>
                        </div>
                        <div className="flex gap-4">
                          <button
                            className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                            onClick={() => {
                              addToCart(item)
                            }}
                          >
                            +
                          </button>
                          <p >{item.quantity}</p>
                          <button
                            className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                            onClick={() => {
                              removeFromCart(item)
                            }}
                          >
                            -
                          </button>
    
                        </div>
                      </div>
                    ))}
                  
                  </div>
              
                  {
                    cartItems.length > 0 ? (
                      <div className="flex flex-col justify-between items-center">
                        <h1 className="text-lg font-bold">Total: ${getCartTotal()}</h1>
                        <button
                          className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                          onClick={() => {
                            clearCart()
                          }}
                        >
                          Clear cart
                        </button>
    {/* check out here */}
    
    
    
    {/* check out here */}
            
    
                        {/* <Button variant="primary" onClick={} >Check out
                          </Button>  */}
                      </div>
    
                    ) : (
                      <>
                        <h1 className="text-lg font-bold">Your cart is empty</h1>
                      </>
                    )
                  }
                </div>
                <div class="col d-flex justify-content-center">
     </div> 
              </>
            </Box>
    
    
            </DialogContent>
            <DialogActions>
              <Button onClick={handleClose}>Close Check Out</Button>
              <Button onClick={handleCheckout} autoFocus>
                Check out and pay
              </Button>
            </DialogActions>
          </Dialog>
        </React.Fragment>
      );
    }