Skip to content

Edvins Antonovs

How to use Redux-Persist with Redux-Toolkit

Recently, I've discovered redux-toolkit which is the official, opinionated, batteries-included toolset for efficient Redux development. It's intended to be the standard way to write Redux logic. Moreover, redux template for create-react-app is now using redux-toolkit by default.

Today, I'd like to share a way to use redux-persist with redux-toolkit. redux-persist gives us an ability to save Redux store in the Local Storage of the browser. Effectively, when you press the refresh page button in your browser, your storage will remain the same. Obviously, you can define how many levels or which parts of your store you want to make persistent.


Initial setup

I will use a fresh copy of the create-react-app with redux-toolkit built-in and jump straight to the action.

1npx create-react-app my-app --template redux

Generated application and file structure

We just genereated our create-react-app and we got this file structure as an output.

1├── README.md
2├── node_modules
3├── package.json
4├── public
5├── src
6└── yarn.lock

We are interested in /src/app folder where we can find store.js file.

1src
2├── App.css
3├── App.js
4├── App.test.js
5├── app
6│   └── store.js
7├── features
8│   └── counter
9│   ├── Counter.js
10│   ├── Counter.module.css
11│   └── counterSlice.js
12├── index.css
13├── index.js
14├── logo.svg
15├── serviceWorker.js
16└── setupTests.js

Now, let's have a quick look at src/app/store.js and src/index.js files. These are 2 files where we will do all of the required changes.

src/app/store.js
1import { configureStore } from '@reduxjs/toolkit';
2import counterReducer from '../features/counter/counterSlice';
3
4export default configureStore({
5 reducer: {
6 counter: counterReducer,
7 },
8});
src/index.js
1// ...
2
3import store from './app/store';
4import { Provider } from 'react-redux';
5
6ReactDOM.render(
7 <React.StrictMode>
8 <Provider store={store}>
9 <App />
10 </Provider>
11 </React.StrictMode>,
12 document.getElementById('root')
13);
14
15// ...

Implementation

First of all, let's add redux-persist as a package (we will need it later) and run the application in development mode.

1yarn add redux-persist
2
3yarn start

Now, we should be up and running, and we should see our counter example application. You can test out the application to see how it works and then hit the page refresh button. Then the state is refreshed back to the normal and you should see 0 counts on the screen. But we want to keep it persistent, right? That's where redux-persist steps in! How to use redux-persist with redux-toolkit

Persistable state

Now let's do some changes to the following files.

The general idea is that on line 18 in src/app/store.js you can define, which reducers you want to make persistent. You need to carefully decide how many levels of state you want to "merge". The default is 1 level. More information - state reconciler docs.

src/app/store.js
1import { configureStore } from '@reduxjs/toolkit';
2import storage from 'redux-persist/lib/storage';
3import { combineReducers } from 'redux';
4import { persistReducer } from 'redux-persist';
5import thunk from 'redux-thunk';
6
7import counterReducer from '../features/counter/counterSlice';
8
9const reducers = combineReducers({
10 counter: counterReducer,
11});
12
13const persistConfig = {
14 key: 'root',
15 storage,
16};
17
18const persistedReducer = persistReducer(persistConfig, reducers);
19
20const store = configureStore({
21 reducer: persistedReducer,
22 devTools: process.env.NODE_ENV !== 'production',
23 middleware: [thunk],
24});
25
26export default store;

Here we add persistStore and PersistGate. PersistGate delays the rendering of your app's UI until your persisted state has been retrieved and saved to redux.

src/index.js
1// ...
2
3import { PersistGate } from 'redux-persist/integration/react';
4import { persistStore } from 'redux-persist';
5
6let persistor = persistStore(store);
7
8ReactDOM.render(
9 <React.StrictMode>
10 <Provider store={store}>
11 <PersistGate loading={null} persistor={persistor}>
12 <App />
13 </PersistGate>
14 </Provider>
15 </React.StrictMode>,
16 document.getElementById('root')
17);
18
19// ...

Now it's time to test our application! Try to increase or decrease the amount and then hit page refresh as well as open your developer console and check the local storage setting, where you should see the amount value in it.


Blacklist and whitelist

To prevent any performance issues, you can decide which parts of the state you want to persist. Take a look at the example below, and amend persistConfig in src/app/store.js as per your needs.

src/app/store.js
1// blacklist
2const persistConfig = {
3 key: 'root',
4 storage: storage,
5 blacklist: ['navigation'], // navigation will not be persisted
6};
7
8// whitelist
9const persistConfig = {
10 key: 'root',
11 storage: storage,
12 whitelist: ['navigation'], // only navigation will be persisted
13};

Appendix

To simplify things, I've added the code above into repository, which you can use as a starter template. Additionally, I've deployed it to Netlify so you could see it in action.

© 2024 by Edvins Antonovs. All rights reserved.