ElendilTheTall
ElendilTheTall

Reputation: 1452

localStorage resets to empty on refresh in NextJS

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?

Upvotes: 2

Views: 4541

Answers (3)

RandomNpc
RandomNpc

Reputation: 41

Another approach that can work:

useLayoutEffect(() => {
    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]);

Upvotes: 0

Anurag Tripathi
Anurag Tripathi

Reputation: 1094

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

Upvotes: 1

Samathingamajig
Samathingamajig

Reputation: 13245

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]);

Upvotes: 5

Related Questions