How to Use Local Storage to Set InitialState in Redux Toolkit for a Next.js Typescript Application

Utilizing localStorage to manage initial state in a Redux Toolkit slice within a Next.js TypeScript application can enhance user experience by retaining state across sessions. However, managing this involves ensuring access to localStorage strictly on the client side and integrating it with Redux Toolkit’s setup.

Below I will provide a step-by-step example of how to create a Redux slice with initial state sourced from localStorage, which is especially useful for settings, user preferences, or session data.

Step 1: Set Up Redux Toolkit in Next.js

First, install Redux Toolkit and React Redux if you haven’t already:

npm install @reduxjs/toolkit react-redux

Step 2: Create the Store

Create a simple Redux store in your Next.js app. Here’s a basic setup:

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';

const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

Step 3: Create a Redux Slice with localStorage for Initial State

We will use a user authentication state as an example. We store the user’s authentication state in localStorage so that it persists across sessions.

// slices/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface UserState {
  isAuthenticated: boolean;
}

// Attempt to retrieve the state from localStorage
const loadState = (): UserState => {
  try {
    const serializedState = localStorage.getItem('userState');
    if (serializedState === null) {
      return { isAuthenticated: false };
    }
    return JSON.parse(serializedState);
  } catch (err) {
    return { isAuthenticated: false };
  }
};

const initialState: UserState = loadState();

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    logIn(state) {
      state.isAuthenticated = true;
      localStorage.setItem('userState', JSON.stringify(state));
    },
    logOut(state) {
      state.isAuthenticated = false;
      localStorage.setItem('userState', JSON.stringify(state));
    },
  },
});

export const { logIn, logOut } = userSlice.actions;

export default userSlice.reducer;

This slice contains two actions: logIn and logOut. Notice also that the initial state is set by attempting to load the state from localStorage using the loadState function. The state is updated in localStorage whenever actions are dispatched.

Step 4: Handling Server-Side Usage

Since localStorage is not accessible during server-side rendering, care must be taken only to interact with localStorage during client-side execution.

The given example already abstracts the localStorage logic safely by keeping it in action creators and default state initialization (which will only run client-side during hydration).

Step 5: Integrate Store with Next.js

Make sure to wrap your application in the Provider from react-redux to allow React components to access the Redux store:

// pages/_app.tsx
import { AppProps } from 'next/app';
import { Provider } from 'react-redux';
import store from '../store';

const MyApp = ({ Component, pageProps }: AppProps) => (
  <Provider store={store}>
    <Component {...pageProps} />
  </Provider>
);

export default MyApp;

Conclusion

This setup ensures that sensitive operations involving localStorage are handled client-side, integrating neatly into Redux Toolkit’s framework and providing a persistence layer for your application’s state. Always test thoroughly to ensure no server-side code attempts to access localStorage, as it will cause errors during rendering.