Redux Persist with Redux Toolkit

There is a pretty handy library called redux-persist that persists the global object from the application’s redux store in the local storage. This is useful when the javascript application breaks on page reaload which happens for example all the time when developing with hot module replacement.

This article explains how to integrate this library in your web application when you are using the redux toolkit. It is an opinionated toolset for redux development that provides really good defaults and it is my prefered way to setup the redux store.

Store Setup

At first comes the definition of the root reducer. To do so rtk provides a hook that is called combineReducers() that is really well documented on the website. The root reducer is than passed to persistReducer with the configuration as the first argument. This function returns the persistedReducer to which you refer in the configureStore() method.

The stuff with the serializable check fixes an error I haven’t fully understand yet. If the regarding actions aren’t ignored in the middleware configuration the whole setup works as well, but you get console errors. The nice thing is that the error message links directly to a code example that fixes the problem.

import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE, persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

import { postApi } from '../services/postApi';
import applicationReducer from './applicationSlice';
import postReducer from './postSlice';

const rootReducer = combineReducers({
  post: postReducer,
  application: applicationReducer,
  [postApi.reducerPath]: postApi.reducer,
});

const persistConfig = {
  key: 'root',
  storage,
  blacklist: ['postApi'],
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(postApi.middleware),
});

setupListeners(store.dispatch);

export default store;

export const persistor = persistStore(store);

Another thing to mention is that the postApi is blacklisted in the given code example. So what happens when there is no information for exampe after page reload is that the query or the mutation gets executed again. I find it better that way because the whole caching mechanism of rtk query is complex enough. I prefer to avoid extra logic that influences the cache.

Adding The Store To The Component Tree

Last thing is to integrate the persisted store in the react application. Redux persist offers the PersistGate component that wrapps the whole component tree as a child of the Provider. The store and the persistor are given to the regarding components and that its done. The redux store object gets persisted in the storage and rehydrates the store on page reload.

import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';

import App from './App';
import store, { persistor } from './store/store';

export default function Index() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <BrowserRouter>
          <Routes>
            <Route path={'app/'} element={<App />}>
                {/* ... */}
            </Route>
          </Routes>
        </BrowserRouter>
      </PersistGate>
    </Provider>
  );
}