1

I have a shopping cart system in my next.js app using Context.

I define my cart with useState:

const [cartItems, setCartItems] = useState([]);

Then I use useEffect to check and update the localStorage:

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

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

This stores the items in localStorage fine, but when I refresh, it resets the cartItems item in localStorage to an empty array. I've seen a few answers where you get the localStorage item before setting the cart state but that throws localStorage is not defined errors in Next. How can I do this?

ElendilTheTall
  • 1,344
  • 15
  • 23

3 Answers3

5

setCartItems sets the value of cartItems for the next render, so on the initial render it's [] during the second useEffect

You can fix this by storing a ref (which doesn't rerender on state change) for whether it's the first render or not.

import React, { useState, useRef } from "react";

// ...

// in component

const initialRender = useRef(true);

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

useEffect(() => {
    if (initialRender.current) {
        initialRender.current = false;
        return;
    }
    window.localStorage.setItem("cartItems", JSON.stringify(cartItems));
}, [cartItems]);
Samathingamajig
  • 11,839
  • 3
  • 12
  • 34
1

React never updates state immediately It's an asynchronous process, for example, if you console.log(stateValue) just after the setState() method you'll get prevState value in a log. You can read more about it here.

That is exactly happening here, you have called setState method inside the first useEffect, state has not updated yet by react and we're trying to update localStorage with the latest state value(for now it's [] since react has not updated the state yet). that's why the localStorage value holds an empty array.

For your case, you can skip the first execution of 2nd useEffect as @Samathingamajig's mentioned in his answer.

PS: Thanks @Samathingamajig for pointing out the silly mistake, I don't know how missed that. LOL

Anurag Tripathi
  • 784
  • 1
  • 13
  • 13
  • 1
    I don't think this will work properly though. On the initial render, `cartItems` will be `[]`. In the first `useEffect`, `cartItems` in localstorage will be set to the empty array. In the second `useEffect`, it will go inside the for loop because an empty array is truthy. The value of `storedCartItems` will be an empty array (because of the first `useEffect`) and the value of `cartItems` will still be an empty array, so the `setCartItems()` will keep `cartItems` as an empty array. – Samathingamajig Aug 08 '22 at 14:07
-1

thanks to jamesmosier for his answer on gitHub

The issue you are seeing is because localStorage (aka window.localStorage) is not defined on the server side. Next server renders your components, so when that happens and it tried to access localStorage it can't find it. You'll have to wait until the browser renders it in order to use localStorage.

full answer link here

EDIT

you can try this :

useEffect(() => {
    if (typeof window !== "undefined") {
      const storedCartItems = JSON.parse(localStorage.getItem("cartItems"));
      if(storedCartItems !== null) {
        setCartItems([...cartItems, ...storedCartItems]);
      }
    }
  }, []);

  useEffect(() => {
    if (typeof window !== "undefined") {
     window.localStorage.setItem("cartItems", JSON.stringify(cartItems));
    } 
  }, [cartItems]);
Omar Dieh
  • 500
  • 3
  • 8
  • 2
    Thanks for your answer. I've been trying your suggestion after seeing other similar questions but it makes no difference unfortunately - it still resets to an empty array. – ElendilTheTall Aug 07 '22 at 21:27
  • 2
    @OmarDeih `useEffect`s are not called serverside so there is no use for that. The problem was that on the initial render OP was setting `"carItems"` to an empty array in localstorage – Samathingamajig Aug 07 '22 at 21:29
  • @ElendilTheTall happy trying to help :) I have updated the example in my answer please take a look. I am not an expert to explain why this is working, but I copied this code from an example that is already running nextjs app and working now while writing to you. hope it helps. cheers :) – Omar Dieh Aug 07 '22 at 21:37
  • @Samathingamajig thanks for explaining ! there is always more to learn ;) – Omar Dieh Aug 07 '22 at 21:37