Search code examples
javascriptreactjslocal-storageundefined

Not able to update CartItems in Local Storage


When i click on addproduct then it gives error of undefined.. here is my ProductDetails.js code Why the product setting to localstorage is undefined

import axios from "axios";
import React, { useEffect, useState } from "react";
import { NavLink, useParams } from "react-router-dom";
import { useDispatch } from "react-redux";
import { addToCart } from "../Redux/action/action";
//import { useSelector } from "react-redux";

import { FaStar } from "react-icons/fa";
import Skeleton from "react-loading-skeleton";

const cartItemsFromStorage =
  JSON.parse(localStorage.getItem("CartItems")) || [];

const ProductDetails = () => {
  const { id } = useParams();
  //const cartItems = useSelector((state) => state.handleCart);
  const [isLoading, setIsLoading] = useState(false);
  const [product, setProduct] = useState(cartItemsFromStorage); 

  const dispatch = useDispatch();

  useEffect(() => {
    const fetchProduct = async () => {
      try {
        setIsLoading(true);
        const { data } = await axios.get(
          `https://fakestoreapi.com/products/${id}`
        );
        setProduct(data);
      } catch (error) {
        console.error("Error fetching product:", error);
      } finally {
        setIsLoading(false);
      }
    };

    fetchProduct();
  }, [id]);

  const addProduct = (product) => {
    if (product) {
      // Update the Redux store
      dispatch(addToCart(product));

      // Retrieve existing cart items from localStorage
      const existingCartItemsJSON = localStorage.getItem("CartItems");
      const existingCartItems = existingCartItemsJSON
        ? JSON.parse(existingCartItemsJSON)
        : [];

      // Ensure that existingCartItems is an array
      if (!Array.isArray(existingCartItems)) {
        console.error("Invalid existingCartItems:", existingCartItems);
        return;
      }

      // Add the new product to the existing cart items
      const updatedCartItems = [...existingCartItems, product];

      // Store the updated cart items back in localStorage
      localStorage.setItem("CartItems", JSON.stringify(updatedCartItems));
    } else {
      console.error("Invalid product:", product);
    }
  };

  const ShowProducts = () => (
    <div className="d-flex row" key={product.id}>
      <div className="col-md-6 col-sm-3 mt-5">
        <img
          src={product.image}
          alt={product.title}
          height="400px"
          width="400px"
        />
      </div>
      <div className="col-md-6 mt-5">
        <h4 className="text-uppercase text-black-50">{product.category}</h4>
        <h1 className="display-5">{product.title}</h1>
        <p className="lead fw-bolder">
          Rating {product.rating && product.rating.rate}
          <FaStar />
        </p>
        <h3 className="display-6 fw-bolder my-4">${product.price}</h3>
        <p className="lead">{product.description}</p>
        <button className="btn btn-primary" onClick={() => addProduct(product)}>
          Add to Cart
        </button>
        <NavLink to="/MyCart" className="btn btn-outline-dark ms-2">
          Go to Cart
        </NavLink>
      </div>
    </div>
  );

  return (
    <>
      <div className="container py-5">
        <div className="row">
          {isLoading ? (
            <>
              {" "}
              <div className="col-md-6">
                <Skeleton height={400} />
              </div>
              <div className="col-md-6">
                <Skeleton width={300} height={50} />
                <Skeleton height={75} />
                <Skeleton width={25} height={150} />
                <Skeleton height={50} />
                <Skeleton height={150} />
                <Skeleton height={50} width={100} />
                <Skeleton height={50} width={100} />
              </div>
            </>
          ) : (
            product && <ShowProducts />
          )}
        </div>
      </div>
    </>
  );
};

export default ProductDetails;

Here is my Reducer.js code Where i defined addToCart() function

import { ADD_TO_CART, REMOVE_FROM_CART } from "../action/action-type";

const cart = []

const handleCart = (state = cart, action) => {
  const product = action.payload;

  switch (action.type) {
    case ADD_TO_CART:
      const existingProduct = state.find((item) => item.id === product.id);

      if (existingProduct) {
        return state.map((item) =>
          item.id === product.id ? { ...item, qty: item.qty + 1 } : item
        );
      } else {
        const product = action.payload;
        return [
          ...state,

          {
            ...product,
            qty: 1,
          },
        ];
      }

    case REMOVE_FROM_CART:
      const existingProductToRemove = state.find(
        (item) => item.id === product.id
      );
      if (existingProductToRemove.qty === 1) {
        return state.filter((item) => item.id !== product.id);
      } else {
        return state.map(
          (item) =>
            item.id === product.id ? { ...item, qty: item.qty - 1 } : item,
          localStorage.setItem("CartItems", JSON.stringify(state.cart))
        );
      }

    default:
      return state;
  }
};

export default handleCart;

when i click on addProduct it is giving undefind error i think it is getting undefined items from local storage or the data sets as undefined.. Please can someone provide me solution stucked in it from 2 days.


Solution

  • The problem is simply because cartItemsFromStorage return an array but you set it to product which is an object. So, the {product && ShowProduct()} always renders, then you try to access things like product.title it will show as undefined

    You just need to refactor a bit to make it works:

    const [product, setProduct] = useState();
    
    useEffect(() => {
      const fetchProduct = async () => {
        // check local storage cache
        // should check cache in useEffect on `id` change
        // otherwise it only gets the cache the first time the file is loaded
        const cartItemsFromStorage =
          JSON.parse(localStorage.getItem("CartItems")) || [];
        const product = cartItemsFromStorage.find((item) => item.id === id);
    
        // if product exists, get data from cache
        if (product) {
          setProduct(product);
          return;
        }
    
        // if product does not exist, fetch api
        try {
          setIsLoading(true);
          const { data } = await axios.get(
            `https://fakestoreapi.com/products/${id}`
          );
          setProduct(data);
        } catch (error) {
          console.error("Error fetching product:", error);
        } finally {
          setIsLoading(false);
        }
      };
    
      fetchProduct();
    }, [id]);
    

    Look at your code I can tell that you are learning React. I would like to suggest some good practices for caching. In professional works, caching should be manage by cache manager package like @tanstack/query or in your case you're using redux, a similar option is redux-query (or @reduxjs/toolkit/query if you're using @reduxjs/toolkit). But it is totally fine to try the manual methods like you did first to understand how React works with browser APIs.

    Have a nice day!