DEV Community

Cover image for Simplest way to persist redux state to localStorage
Igor Petrovic
Igor Petrovic

Posted on

Simplest way to persist redux state to localStorage

For a next.js web app, I needed to persist the redux state to the browser's localStorage. In this article, I assume you already know how to setup redux in a next.js app.

Why should I persist the state ?

In this project, there is no database or user session. But still, the user should be able to save and resume its work on each visit to the app.

Demo app

It's a simple ToDo app to demonstrate this technic. You can check it on stackbitz.com here
Todo app stackblitz

Let's see the code

What we need first is two function to serialize and deserialize redux state.

app/browser-storage.ts

const KEY = "redux";
export function loadState() {
  try {
    const serializedState = localStorage.getItem(KEY);
    if (!serializedState) return undefined;
    return JSON.parse(serializedState);
  } catch (e) {
    return undefined;
  }
}

export async function saveState(state: any) {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(KEY, serializedState);
  } catch (e) {
    // Ignore
  }
}
Enter fullscreen mode Exit fullscreen mode

Then in pages/_app.tsx we subscribe to the store so we can save it each time a change happens.

pages/_app.tsx

import "tailwindcss/tailwind.css";
import type { AppProps } from "next/app";
import { Provider } from "react-redux";
import { saveState } from "app/browser-storage";
import { debounce } from "debounce";
import { store } from "app/store";

// here we subscribe to the store changes
store.subscribe(
  // we use debounce to save the state once each 800ms
  // for better performances in case multiple changes occur in a short time
  debounce(() => {
    saveState(store.getState());
  }, 800)
);

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}
export default MyApp;
Enter fullscreen mode Exit fullscreen mode

The last part is how to restore the saved state when the user comes back. This is done in the store configuration.
We use reduxjs/toolkit configureStore and it's preloadedState configuration property.

app/store.ts

import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";

import todosReducer from "./feature/todo";
import { loadState } from "./browser-storage";

const reducers = combineReducers({
  todos: todosReducer,
});

export const store = configureStore({
  devTools: true,
  reducer: reducers,
  // here we restore the previously persisted state
  preloadedState: loadState(),
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
Enter fullscreen mode Exit fullscreen mode

Cons

  • Using localStorage to persist app's state could impact its performance especially if you have a large state.
  • localStorage is not a bulletproof solution. If the user resets its browser cache app state is lost.

Github

The code is available here github

Notes

No relative imports

You probably noticed that my imports don't use relative paths like ../app/store but app/store this is done by configuring your tsconfig.json with compilerOptions baseUrl.

{
  "compilerOptions": {
    "baseUrl": "."
  },
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)