loading...

Next.js persistent state with React hooks and localStorage. How to make it work?

jaklaudiusz profile image Klaudiusz ・1 min read

I'm trying to create persistent state with localStorage and React hooks in Next.js SSR app and everything seems to look good but when I reload page after update my data I'm getting error:

Warning: Text content did not match. Server: "0" Client: "5

What can I do to repair that? This is my code:

//  src/utils/Store.js

import React, { createContext, useContext, useReducer, useEffect } from "react";
import lscache from "lscache";
import Reducer, {initialState as defaultState} from "../utils/Reducer";
const StoreContext = createContext();

const initialState = lscache.get('state') ? lscache.get('state') : defaultState;

 export const StoreProvider = ({ children }) => {

  let [state, dispatch] = useReducer(Reducer, initialState);

  useEffect(
    function saveStateToLocalStorage() {
      lscache.set('state', state) 
    },
    [state]
  );

  const updateStateFromLocalStorage = () => {
    const newState = lscache.get('state') ? lscache.get('state') : defaultState;
    dispatch({ type: "updatefromlocalstorage", newState });
  };

  useEffect(function watchForChanges() {
      window.addEventListener("storage", updateStateFromLocalStorage);
    return () => {
      window.removeEventListener("storage", updateStateFromLocalStorage);
    };
  }, []);

  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
};

export const useStore = () => useContext(StoreContext); 


Working example in codesandbox:

Edit intelligent-lamport-s0hu8

Posted on by:

Discussion

markdown guide
 

Hello Klaudiusz!

I found your thread on Google trying to solve mine 😂

I don't have a similar issue but I successfully did something similar, I figure, it's better that I post it than not at all.

export const PostsContextProvider = ({ children, storageKey = "posts" }) => {
  const [isInitialized, setIsInitialized] = useState(false);

  const [posts, dispatchPost] = useReducer(reducer, []);

  useEffect(() => {
    if (isInitialized) {
      localStorage.setItem(storageKey, JSON.stringify(posts));
    }
  }, [posts]);

  useEffect(() => {
    dispatchPost({
        type: SET_POSTS,
        value: JSON.parse(localStorage.getItem(storageKey)) || []
      });

    setIsInitialized(true);
  }, []);

  return (
    <PostsContext.Provider value={{ posts, dispatchPost }}>
      {children}
    </PostsContext.Provider>
  );
};

As you can see, my version use 2 useEffect. One to initialized the data using the localStorage if it's there is something (otherwise, it defaults to []) and one to keep the localStorage updated with the app.

Happy to answer question if you have some 👍