Home > Software engineering >  React.js: cannot get all items from localStorage using useEffect and useContext
React.js: cannot get all items from localStorage using useEffect and useContext

Time:10-18

for learning purposes, I'm creating an e-shop, but I got stuck with localStorage, useEffect, and React context. Basically, I have a product catalog with a button for every item there that should add a product to the cart.

It also creates an object in localStorage with that item's id and amount, which you select when adding the product to the cart.

My context file:

 import * as React from 'react';

 const CartContext = React.createContext();

 export const CartProvider = ({ children }) => {
   const [cartProducts, setCartProducts] = React.useState([]);

 const handleAddtoCart = React.useCallback((product) => {
   setCartProducts([...cartProducts, product]);
   localStorage.setItem('cartProductsObj', JSON.stringify([...cartProducts, product]));
 }, [cartProducts]);

 const cartContextValue = React.useMemo(() => ({
   cartProducts,
   addToCart: handleAddtoCart, // addToCart is added to the button which adds the product to the cart

 }), [cartProducts, handleAddtoCart]);

 return (
   <CartContext.Provider value={cartContextValue}>{children}</CartContext.Provider>
   );
 };

export default CartContext;

When multiple products are added, then they're correctly displayed in localStorage. I tried to log the cartProducts in the console after adding multiple, but then only the most recent one is logged, even though there are multiple in localStorage.

My component where I'm facing the issue:

const CartProduct = () => {
  const { cartProducts: cartProductsData } = React.useContext(CartContext);
  const [cartProducts, setCartProducts] = React.useState([]);

  React.useEffect(() => {
    (async () => {
      const productsObj = localStorage.getItem('cartProductsObj');
      const retrievedProducts = JSON.parse(productsObj);

      if (productsObj) {
        Object.values(retrievedProducts).forEach(async (x) => {
          const fetchedProduct = await ProductService.fetchProductById(x.id);
          setCartProducts([...cartProducts, fetchedProduct]);
        });
      }
    }
  )();
 }, []);

 console.log('cartProducts', cartProducts);

 return (
   <>
    <pre>
      {JSON.stringify(cartProductsData, null, 4)}
    </pre>
   </>
  );
 };

 export default CartProduct;

My service file with fetchProductById function:

const domain = 'http://localhost:8000';
const databaseCollection = 'api/products';
const relationsParams = 'joinBy=categoryId&joinBy=typeId';

const fetchProductById = async (id) => {
  const response = await fetch(`${domain}/${databaseCollection}/${id}?${relationsParams}`);
  const product = await response.json();

  return product;
};

const ProductService = {
  fetchProductById,
};

export default ProductService;

As of now I just want to see all the products that I added to the cart in the console, but I can only see the most recent one. Can anyone see my mistake? Or maybe there's something that I missed?

CodePudding user response:

Print the cartProducts inside useEffect to see if you see all the data

useEffect(() => {
    console.log('cartProducts', cartProducts);
}, [cartProducts]);

CodePudding user response:

This looks bad:

Object.values(retrievedProducts).forEach(async (x) => {
  const fetchedProduct = await ProductService.fetchProductById(x.id);
  setCartProducts([...cartProducts, fetchedProduct]);
});

You run a loop, but cartProducts has the same value in every iteration

Either do this:

Object.values(retrievedProducts).forEach(async (x) => {
  const fetchedProduct = await ProductService.fetchProductById(x.id);
  setCartProducts(cartProducts => [...cartProducts, fetchedProduct]);
});

Or this:

const values = Promise.all(Object.values(retrievedProducts).map(x => ProductService.fetchProductById(x.id)));
setCartProducts(values)

The last is better because it makes less state updates

CodePudding user response:

Issue

When you call a state setter multiple times in a loop for example like in your case, React uses what's called Automatic Batching, and hence only the last call of a given state setter called multiple times apply.

Solution

In your useEffect in CartProduct component, call setCartProducts giving it a function updater, like so:

setCartProducts([...cartProducts, fetchedProduct]);

The function updater gets always the recent state even though React has not re-rendered. React documentation says:

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

CodePudding user response:

if this line its returning corrects values

const productsObj = localStorage.getItem('cartProductsObj');

then the wrong will be in the if conditional: replace with

(async () => {
      const productsObj = localStorage.getItem('cartProductsObj');
      const retrievedProducts = JSON.parse(productsObj);

      if (productsObj) {
       Object.values(retrievedProducts).forEach(async (x) => {
        const fetched = await ProductService.fetchProductById(x.id);
        setCartProducts(cartProducts => [...fetched, fetchedProduct]);
       });
      }
    }
  • Related