Making Redux State Persistent with Redux Toolkit and redux-persist

Effortlessly Persist Redux State Across Page Reloads

Introduction

Have you ever been in the middle of something important on a website, only to accidentally refresh the page and lose all your progress? It's frustrating, right? As developers, we aim to create smooth, user-friendly experiences, and one way to achieve that is by ensuring the app state persists across page reloads. Enter redux-persist.

By integrating redux-persist with Redux Toolkit, we can easily save our Redux state to local storage (or other storage options). This means users can pick up right where they left off, even after a page reload. Let's dive into how to set this up and make our applications more resilient.

Why Should We Use redux-persist?

  1. State Persistence: Have you ever filled out a long form, accidentally refreshed the page, and had to start over? redux-persist helps avoid this by keeping your app's state intact across reloads.

  2. Enhanced User Experience: By retaining state, we can create a more seamless and enjoyable experience for users. No more lost data or interrupted workflows.

  3. Reduced Server Load: By storing some state on the client side, we reduce the need for frequent server requests. This can improve performance and save on server costs.

  4. Simple Integration: With Redux Toolkit, integrating redux-persist is straightforward and clean, making state management a breeze.

Step-by-Step Guide to Setting Up Redux with redux-persist

1. Define Your Slices

Slices in Redux Toolkit are a way to manage a single piece of your app's state. Think of it like a slice of the state pie. Here's an example of an authSlice for handling authentication:

import { createSlice } from '@reduxjs/toolkit';
const initialState = {
    user: null,
    token: null,
  }
const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setUser(state, action) {
      state.user = action.payload;
    },
    setToken(state, action) {
      state.token = action.payload;
    },
    clearAuth(state) {
      state.user = null;
      state.token = null;
    },
  },
});

export const { setUser, setToken, clearAuth } = authSlice.actions;
export const authReducer = authSlice.reducer;

This slice includes the initial state and actions to update the state, such as setting and clearing the user.

2. Configure redux-persist

Next, we need to set up redux-persist to save our authentication state. This involves creating a persist configuration and a persisted reducer.

import { configureStore } from '@reduxjs/toolkit';
import { authReducer } from './features/auth/authSlice';
import { baseApi } from './api/baseApi';
import storage from 'redux-persist/lib/storage';
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';

// Define persist configuration for the auth slice
const authPersistConfig = {
  key: 'auth',
  storage: storage,
};

// Create a persisted reducer
const persistedReducer = persistReducer(authPersistConfig, authReducer);

// Configure the Redux store
export const store = configureStore({
  reducer: {
    [baseApi.reducerPath]: baseApi.reducer,
    auth: persistedReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(baseApi.middleware),
});

// Create a persistor
export const persistor = persistStore(store);

// Infer types for the Redux state and dispatch
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Breaking Down the Key Concepts

  1. Persist Configuration: This configuration tells redux-persist how and where to save the state. The key specifies the key in storage, and storage specifies the storage engine (like local storage).

  2. Persisted Reducer: This is our original reducer wrapped in persistReducer, which handles the saving and loading of the state.

  3. Configure Store: Using Redux Toolkit's configureStore, we set up our store with the persisted reducer and apply necessary middleware. The serializableCheck option ensures we ignore certain redux-persist actions during serialization checks to avoid errors.

  4. Persistor: The persistStore function creates a persistor linked to the Redux store, managing the persistence lifecycle, including saving and rehydrating the state.

Conclusion

By integrating Redux Toolkit with redux-persist, we can effortlessly enhance our applications, ensuring a smoother and more reliable user experience. No more worries about losing state on page reloads! This setup not only simplifies state management but also makes our apps more robust and user-friendly. Give it a try and see how it transforms your development workflow.