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

Discussion

pic
Editor guide
Collapse
nvio profile image
Steven Yung

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 πŸ‘