Simplest way to persist redux state to localStorage
Tags: nextjs, localstorage, redux
Published: 2021-09-22 09:07:46

> 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](https://stackblitz.com/edit/github-sp72vl?file=pages/_app.tsx)

## Let's see the code
What we need first is two function to `serialize` and `deserialize` redux state. 

**app/browser-storage.ts**
```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
  }
}
```

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

**pages/_app.tsx**
```ts
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;
```

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**
```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;
```

## 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](https://github.com/igorovic/next-redux-localstorage)


## 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`. 

```json
{
  "compilerOptions": {
    "baseUrl": "."
  },
}
```