Editor’s note: This article was last updated by Timonwa Akintokun on 10 September 2024 to discuss properties that selectively persist parts of a state, such as blacklist
and whitelist
, as well as to cover createListenerMiddleware
for managing side effects and coordinating async actions in Redux Toolkit, and autoBatchEnhancer
for optimizing performance.
With the Redux Persist library, developers can save the Redux store in persistent storage, such as local storage. That way, even after refreshing the browser, the site state will be preserved. Redux Persist also includes methods that allow us to customize the state that gets persisted and rehydrated, all with an easily understandable API.
In this article, we’ll learn how to use Redux Persist with Redux Toolkit in React. To follow along with this article, you should be familiar with React and Redux Toolkit. You should also have Node.js installed on your machine.
Redux Persist is a state management tool that allows the state in a Redux store to persist across browser and app sessions, improving user experience by pre-loading the store with persistent data. It also offers protection against unexpected crashes and network issues, preventing data loss and offering a more reliable user experience.
Redux Persist offers various configurations, including custom caching strategies, deciding which parts of the state to persist and exclude, and the storage mechanism to use. It also comes with built-in features such as migrations, transforms, and custom merges.
Offline support is another advantage of persisting the Redux store in mobile applications. Persistent state allows users to interact with the app even when offline, and when the network connection is restored, the stored state is rehydrated.
Despite the absence of recent updates, Redux Persist has a large and active community with excellent documentation and readily available answers to common problems. It also works well with the Redux Toolkit, providing a more streamlined and effective state management experience.
I’ve already created an app that uses Redux Toolkit for state management. We’ll use it in this tutorial to learn how Redux Persist works. To start, clone the GitHub repo. You can do so with the following commands:
$ git clone https://github.com/Tammibriggs/auth-app.git $ cd auth-app $ npm install
Next, we can start the app with the npm start
command. In our app, we’ll see a form that has a field to enter our name and email. After entering the required inputs and submitting the form, we’ll be taken to our profile page, which will look similar to the following image:
When we refresh the browser, our data will be lost.
Let’s learn how to use Redux Persist to save the state in persistent storage so that even after a refresh, the data will remain intact. We‘ll also learn how to customize what’s persisted and specify how incoming states will be merged.
First, we’ll add Redux Persist to our app with the following command:
$ npm i redux-persist
Next, we need to modify our store, which we’ll find in the redux
folder in the src
directory of the cloned app. Currently, our store looks like the code below:
// src/redux/store.js import { configureStore } from "@reduxjs/toolkit"; import userReducer from "./slices/userSlice"; export const store = configureStore({ reducer: userReducer, })
To use Redux Persist, we’ll make the following modifications to our store.js
file:
// src/redux/store.js import { configureStore } from "@reduxjs/toolkit"; import { persistStore, persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import userReducer from "./slices/userSlice"; const persistConfig = { key: "root", storage, }; const persistedReducer = persistReducer(persistConfig, userReducer); export const store = configureStore({ reducer: persistedReducer, devTools: process.env.NODE_ENV !== "production", middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"], }, }), }); export const persistor = persistStore(store);
In the code above, we replaced the value of the reducer
property in the store from userReducer
to persistedReducer
, which is an enhanced reducer with a configuration to persist the userReducer
state to local storage. Aside from local storage, we can also use other storage engines like sessionStorage
and Redux Persist Cookie Storage Adapter.
To use a different storage engine, we need to modify the value of the storage
property of persistConfig
with the storage engine we want to use. For example, to use the sessionStorage
engine, we’ll first import it as follows:
import storageSession from 'redux-persist/lib/storage/session'
Then, modify persistConfig
to look like the following code:
const persistConfig = { key: 'root', storageSession, }
In the store configuration, we are including the getDefaultMiddleware
because Redux Persist automatically sets up some middleware, which can introduce non-serializable values during rehydration in the console.
Finally, we passed our store as a parameter to persistStore
, which is the function that persists and rehydrates the state. With this function, our store will be saved to the local storage, and even after a browser refresh, our data will remain.
In most use cases, we might want to delay the rendering of our app’s UI until the persisted data is available in the Redux store. For that, Redux Persist includes the PersistGate
component. To use PersistGate
, go to the index.js
file in the src
directory and add the following import:
// src/index.js import { persistor, store } from './redux/store'; import { PersistGate } from 'redux-persist/integration/react';
Now, modify the render
function call to look like the code below:
// src/index.js root.render( <React.StrictMode> <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <App /> </PersistGate> </Provider> </React.StrictMode> );
In this section, we covered the basic setup when using Redux Persist. Now, let’s explore the available options and use cases for Redux Persist.
Although Redux Persist offers a simple method for persisting the entire store, advanced techniques can be used to manage more complex persistence situations to enhance the robustness and flexibility of state persistence. The following are some of these persistence techniques.
If we have two or more reducers in Redux Toolkit, like userReducer
and notesReducer
, and we want to add them to our store, we’ll likely configure the store as follows:
const store = configureStore({ reducer: { user: userReducer, notes: notesReducer }, })
We can also use combineReducers
, which does the same thing:
const rootReducer = combineReducers({ user: userReducer, notes: NotesReducer }) const store = configureStore({ reducer: rootReducer })
To use Redux Persist in this case, we’ll supply rootReducer
as a parameter of persistReducer
, then replace rootReducer
in our store with the persisted reducer as follows:
const rootReducer = combineReducers({ user: userReducer, notes: NotesReducer }) const persistedReducer = persistReducer(persistConfig, rootReducer) const store = configureStore({ reducer: persistedReducer })
However, what if we want to set a different configuration? For example, let’s say we want to change the storage engine for userReducer
to sessionStorage
. To do so, we can use nested persists, a feature that allows us to nest persistReducer
, giving us the ability to set different configurations for reducers.
Below is an example of a nested persist where I’m changing the storage of the userReducer
to sessionStorage
:
const rootPersistConfig = { key: 'root', storage, } const userPersistConfig = { key: 'user', storage: storageSession, } const rootReducer = combineReducers({ user: persistReducer(userPersistConfig, userReducer), notes: notesReducer }) const persistedReducer = persistReducer(rootPersistConfig, rootReducer) const store = configureStore({ reducer: persistedReducer })
There might be situations when you want to customize how certain parts of your state persist. Transforming persistent data becomes important at this point because it gives you control over what is stored, how it is stored, and if it is stored after being rehydrated. This transformation technique comes in handy in various scenarios, from formatting data and handling defaults to applying pre-processing tasks and so on.
Because the Redux store is configured using the configureStore
function, it specifies reducers for the user
and notes
slices of the state. Let’s fine-tune the persistence behavior for each slice of the state. To do this, we will leverage Redux Persist’s configuration options to define transforms:
import { createTransform } from 'redux-persist'; const notesTransform = createTransform( (incomingState, originalState) => { // Transform the incoming state return { notes: incomingState.notes.map(note => ({ // Apply transformations to each note if needed ...note, content: note.content.toUpperCase(), // Example: Transform content to uppercase })), }; }, (state) => { // Transform the state before persisting return { notes: state.notes.map(note => ({ ...note, content: note.content.toLowerCase(), // Example: Transform content to lowercase })), }; }, { whitelist: ["notes"]} );
Here, two transformations are defined using the createTransform
function: one for the incoming (rehydrated) state and another for the outgoing (persisted) state.
The first function modifies the content of each note, converting it to uppercase before integrating it into the Redux store. The second function modifies the content of each note and is transformed to lowercase before persisting the state.
The createTransform
function also accepts an options object, which includes a whitelist
property. This ensures that only the designated area of the state receives the custom transformations. In this instance, only the notes
slice is approved for transformation.
To take effect, transforms need to be added to a PersistReducer
’s config object:
const persistConfig = { key: 'root', storage, transforms: [notesTransform], }
By incorporating these transforms into your redux-persist
configuration, you gain fine control over how data is persisted and rehydrated, allowing for greater flexibility and customization of your Redux store’s persistent data handling.
After transforming and rehydrating the state, you can easily use it in your React components. For example, you might access and render the transformed notes like this:
import { useSelector } from 'react-redux'; function NotesComponent() { const notes = useSelector((state) => state.notes); // Access transformed state return ( <div> {notes.map(note => ( <div key={note.id}>{note.content}</div> // Render transformed content ))} </div> ); }
With this, you can easily display the transformed and persisted data in your application.
createMigrate
in Redux PersistRedux Persist provides a powerful feature called createMigrate
that allows you to implement synchronous migrations and ensure a seamless transition of your persisted data across different versions of your application’s state.
The createMigrate
feature is useful when introducing changes to the data structure or the application’s reducer logic. As your application grows, so does its state structure. As time passes, you might introduce changes to the state shape, add or remove fields, or carry out other modifications.
It is essential to have a technique in place when working with persistent data that gracefully handles these changes to avoid data corruption or application crashes caused by incompatible state versions.
To demonstrate the usage of the createMigrate
feature, consider a situation where the user
reducer’s structure evolves. Initially, the user
reducer stores basic user information, including name, email, and address. However, a new requirement arises to store additional user details, such as phone number and date of birth.
To incorporate these changes, the user
reducer is updated to include the new fields. However, this update necessitates a migration to seamlessly handle the transition from the old data structure to the new one.
Here’s how to use createMigrate
to implement a synchronous migration:
const migrations = { 1: (state) => { // Migration logic for version 1 return { ...state, phoneNumber: '', dateOfBirth: null, }; }, 2: (state) => { // Migration logic for version 2 return { ...state, nationality: '', }; }, };
Here, the phoneNumber
and dateOfBirth
fields are checked to see if they are present in the state by the version 1
migration function. If not, null
values or empty strings are added, accordingly. This guarantees a smooth transition from the previous state structure to the new one.
To ensure a smooth transition, incorporate migration functions for every version of your application as you release new versions. Your state is currently represented by the version in persistConfig
.
To take the migration effect, version
and migrate
need to be added to a PersistReducer
’s config object:
import { createMigrate } from "redux-persist"; const persistConfig = { key: 'root', storage, version: 1, // Set the current version of your state migrate: createMigrate(migrations, {debug: false}), // Apply migrations };
Redux Persist’s createMigrate
tool allows developers to handle data migrations with ease, ensuring a seamless transition between iterations of the persisted state and preserving data integrity throughout the application’s lifecycle.
Merging involves saving the persisted state back in the Redux store. When our app launches, our initial state is set. Shortly after, Redux Persist retrieves our persisted state from storage and overrides any initial state. This process works automatically.
By default, the merging process auto-merges one level deep. Let’s say we have an incoming and initial state like the following:
{user: {name: 'Tammibriggs'}, isLoggedIn: true} // incoming state { user: {name: '', email: ''}, isLoggedIn: false, status: 'Pending'} // initial state
The merged state will look like this code:
{ user: {name: 'Tammibriggs'}, isLoggedIn: true, status: 'Pending'} // reconciled/merged state
The initial state was merged with the incoming state and the top-level property values. In the incoming state, these are replaced and not merged, which is why the email
property in user
was lost. In our code, this will look similar to the following:
const mergedState = { ...initialState }; mergedState['user'] = persistedState['user'] mergedState['isLoggedIn'] = persistedState['isLoggedIn']
This type of merging in Redux Persist is called autoMergeLevel1
, and it is the default state reconciler in Redux Persist. Other state reconcilers include hardSet
, which completely overrides the initial state with the incoming state, and autoMergeLevel2
, which merges two levels deep.
In our previous example, the email
property in user
won’t be lost. The reconciled or merged state will look like the following code:
{ user: {name: 'Tammibriggs' email:''}, isLoggedIn: true, status: 'Pending'} // reconciled/merged state
For example, to set up a state reconciler, if we want to use autoMergeLevel2
, we just need to specify a stateReconciler
property in persistConfig
:
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; const persistConfig = { key: 'root', storage, stateReconciler: autoMergeLevel2 }
autoBatchEnhancer
The autoBatchEnhancer
— a Redux store enhancer — is included by default in the configureStore
; it helps you improve your application’s performance and works well with Redux Persist. When you have actions that are dispatched in quick succession, instead of it triggering multiple re-renders, Redux will batch these updates and render the UI only once, enhancing the performance of your app, even when numerous state changes occur rapidly.
createListenerMiddleware
createListenerMiddleware
is a newer tool in Redux Toolkit that manages side effects and async actions, offering a more structured approach than the traditional Redux middleware like Redux Thunk or Redux Saga. This middleware allows you to listen for specific actions and dispatch new ones or trigger side effects when these actions occur.
Unlike in traditional middleware, where they intercept the actions and require more manual boilerplate to handle async actions, listenerMiddleware
helps you to simplify the process, allowing you to subscribe directly to the actions and trigger the side effects in response.
Let’s look at how createListenerMiddleware
contrasts with the traditional Redux middleware:
First, while traditional Redux middleware can be very powerful, it requires further setups to handle async flows. createListenerMiddleware
, on the other hand, allows you to respond to actions and run them asynchronously without much setup.
Secondly, createListenerMiddleware
enables more dynamic handling of side effects as it listens for specific actions rather than wrapping the entire dispatch process.
Finally, if you have a complex workflow, you can manage it with createListenerMiddleware
by controlling the side effects using multiple listeners and improving the overall organization of your async logic.
Here is an example of using createListenerMiddleware
:
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'; import { userSlice } from './slices/userSlice'; const listenerMiddleware = createListenerMiddleware(); listenerMiddleware.startListening({ actionCreator: userSlice.actions.signIn, effect: async (action, listenerApi) => { const response = await fetchUserDetails(action.payload); listenerApi.dispatch(userSlice.actions.setUserDetails(response)); } }); const store = configureStore({ reducer: { user: userSlice.reducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware), });
In this example, createListenerMiddleware
listens for the signIn
action and triggers an async side effect, which fetches the additional user details and dispatches them back into the store while still maintaining the clarity of Redux Toolkit’s API.
We can customize a part of our state to persist by using the blacklist
and whitelist
properties of the config
object passed to persistReducer
. With the blacklist
property, we can specify which part of the state does not persist, while the whitelist
property does the opposite, specifying which part of the state to persist.
For example, let’s say we have the following reducers:
const rootReducer = combineReducers({ user: userReducer, notes: notesReducer })
If we want to prevent notes
from persisting, the config
object should look like the following:
const rootPersistConfig = { key: 'root', storage, blacklist: ['notes'] }
And if you want the users
state to always be persisted, you would indicate it in the config
object like so:
const rootPersistConfig = { key: 'root', storage, whitelist: ['users'] }
The blacklist
and whitelist
properties take an array of strings. Each string must match a part of the state that is managed by the reducer we pass to persistReducer
. When using blacklist
and whitelist
, we can only target one level deep. But if we want to target a property in one of our states above, we can take advantage of nested persists.
For example, let’s say the userReducer
initial state looks like the following:
const initialState = { user: {}, isLoggedIn: false, }
If we want to prevent isLoggedIn
from persisting, our code will look like the following:
const rootPersistConfig = { key: 'root', storage, } const userPersistConfig = { key: 'user', storage, blacklist: ['isLoggedIn'] } const rootReducer = combineReducers({ user: persistReducer(userPersistConfig, userReducer), notes: notesReducer }) const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
Now, the isLoggedIn
property won’t persist.
Redux-based React applications depend heavily on state hydration and rehydration, particularly when it comes to persistent storage and app initialization. Libraries like Redux Persist frequently handle this process, which makes managing application state across sessions easier.
State hydration is the process of retrieving previously persisted states from storage (e.g., localStorage
, sessionStorage
, IndexedDB) and storing it in the Redux store. This enhances the user experience by enabling the application to return to its previous state even after a reload. In the context of Redux and React, it involves re-establishing the state of the application, including the data and UI, following a page reload or app restart.
Redux Persist handles the Redux store’s storage and retrieval automatically, making state persistence and retrieval easier. This is accomplished by retrieving the Redux store upon application startup from a storage engine of choice (localStorage
, sessionStorage
, etc.).
Here’s how Redux Toolkit enables hydration for Redux Persist:
persistReducer
to wrap the root reducer. This function accepts two arguments: the actual reducer and a config object with persistence configurationstorage
: Indicates the type of storage medium (localStorage
, for example)key
: Indicates the key that the state is kept underwhitelist</code>
or <code>blacklist
: Keeps specific areas of the statepersistReducer
as the root reducer and configureStore
from the Redux ToolkitPersistGate
around the app: This part serves as a barrier, keeping the application from rendering until the state has been rehydratedHere is a code sample that illustrates state hydration:
// Import necessary functions and modules from Redux and other libraries import { configureStore } from "@reduxjs/toolkit"; import { persistStore, persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import rootReducer from "./slices/rootSlice"; // Configuration for Redux-persist to persist specific parts of the Redux store const persistConfig = { key: "root", // Key for the persisted state in storage storage, // Storage engine (e.g., localStorage) whitelist: ["auth"], // Array of reducers to persist (only 'auth' in this case) }; // Create a persisted reducer using Redux-persist const persistedReducer = persistReducer(persistConfig, rootReducer); // Create the Redux store with the persisted reducer const store = configureStore({ reducer: persistedReducer, }); // Create a persistor to persist the Redux store const persistor = persistStore(store); // Wrap the root component of the application with Redux Provider and PersistGate const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> {/* PersistGate delays rendering the child components until the persisted state is retrieved */} <PersistGate loading={null} persistor={persistor}> <App /> </PersistGate> </Provider> </React.StrictMode> );
Redux Persist takes the information in the Redux state (using JSON.stringify
) and saves it to a specified storage engine (e.g., localStorage
or AsyncStorage
for React Native). This saved information stays even if you close the app or browser. When the application initializes, Redux Persist gets the saved information, turns it back into the original format (using JSON.parse
), and uses it to set up the app the way it was before.
Redux Persist then dispatches a rehydration action to your Redux store, which contains the rehydrated state:
persistor.purge(); // optional: clear storage if needed persistor.flush(); // optional: flush storage to make sure state is persisted immediately
The rehydration action triggers your reducers to update the application state with the rehydrated state.
REHYDRATE
actionAfter retrieving the persisted state, Redux Persist performs a special REHYDRATE
action. This action sends the rehydrated state payload to your reducers, allowing them to update the application state accordingly. React components render according to the rehydrated state in response to the rehydration process.
At this point, your Redux store is initialized with the persisted state. This guarantees that the state saved in the previous session is reflected in the user interface.
Here’s how hydration works using the REHYDRATE
action:
persistStore
REHYDRATE
actionpersistReducer
, which watches for the rehydrate actionHere is a sample of code that illustrates using REHYDRATE
in a specific reducer:
const rootReducer = (state = initialState, action) => { switch (action.type) { case "ADD_NOTES": return { ...state, notes: [...state.notes, action.payload] }; case "REHYDRATE": // Handle rehydration based on the action payload return { ...state, ...action.payload }; default: return state; } };
This process ensures that your application always begins in the correct state, even after a reload. However, it was typical in older versions of Redux Persist to control the rehydration process by catching the REHYDRATE
action in your reducers and then saving the action’s payload to your redux state.
Redux Persist initiates the REHYDRATE
action as soon as your stored state is retrieved and it will be the finalized state if you return a new state object from the REHYDRATE
action. You no longer need to perform this because Redux Persist handles hydration automatically in the most recent version unless you require a customized rehydration process for your state.
REHYDRATE
actionEven when a persisted state is present, there are situations in which you might want to recreate the state. Recreation is the process of re-creating the Redux store from its persistent state. In most cases, middleware is used, reducers are combined, and an initial state is set.
Recreation can be triggered by a variety of actions, such as starting the app, refreshing a page, or navigating to a new page. Redux Persist uses the REHYDRATE
action to accomplish this. When Redux Persist successfully gets and applies the persistent state to the store, it triggers the REHYDRATE
action.
REHYDRATE
in Redux ToolkitRedux Persist also allows you to handle rehydration in the Redux Toolkit if you are writing your reducer using the extraReducers
object:
import { createSlice } from "@reduxjs/toolkit"; import { REHYDRATE } from "redux-persist"; const initialState = { user: {}, isLoggedIn: false, } const userSlice = createSlice({ name: 'userSlice', initialState, reducers: { signIn: (state, action) => { state.user = {...state.user, ...action.payload} state.isLoggedIn = true }, signOut: (state) => { state.user = {} state.isLoggedIn = false } }, extraReducers: (builder) => { builder.addCase(REHYDRATE, (state) => { if (state.user) { state.isLoggedIn = true } }) } }) export const {signIn, signOut} = userSlice.actions export default userSlice.reducer
In the latest version of Redux Toolkit, it’s recommended that you use the builder callback pattern to define extraReducers
. This is because the object syntax for createSlice
and createReducer
is now deprecated.
In this tutorial, we learned how to use Redux Persist in Redux Toolkit to save our data in persistent storage so our data will remain even after a browser refresh. We explored several options for customizing Redux Persist, and advanced techniques such as nested persists and transforming persisted data.
We also explored how state hydration works, how Redux Persist retrieves state from persistent storage and initializes the app state with it upon loading, and how Redux Persist works with recreation by using the REHYDRATE
action in the context of Redux and React apps.
Although at the time of writing, Redux Persist is under maintenance and has not been updated for some time, it is still a great tool with strong community support. I hope you enjoyed this tutorial, and be sure to leave a comment if you have any questions.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBackdrop and background have similar meanings, as they both refer to the area behind something. The main difference is that […]
AI tools like IBM API Connect and Postbot can streamline writing and executing API tests and guard against AI hallucinations or other complications.
Explore DOM manipulation patterns in JavaScript, such as choosing the right querySelector, caching elements, improving event handling, and more.
`window.ai` integrates AI capabilities directly into the browser for more sophisticated client-side functionality without relying heavily on server-side processing.
7 Replies to "Persist state with Redux Persist using Redux Toolkit in React"
I’m amazed that a developer suggests to use a package that hasn’t been updated for 3 years. Redux, React has moved on – many people that tried this package failed for that reason. But even if that wouldn’t already be the case, do you really want to recommend to developers a strategy that is doomed sooner or later?
Very nice tutorial, everthing was on point
PersistGate is not working in my project
it works, works!
I’ve got error: “File ‘/userSlice.ts’ is not a module”.
the top code has a lot of issue:
by writing bottom code you can implement currectly:
my store folder structure fro react redux:
store __ features __ user.ts
| | __ auth.ts
| __ reducer.ts
| __ store.ts
————————————————————————————–
// features folder => user.ts
import { createSlice } from “@reduxjs/toolkit”;
import storage from “redux-persist/lib/storage”;
import { persistReducer } from “redux-persist”;
export const userSlice = createSlice({
name: “user”,
initialState: {
fullname: “jack abc”,
age: 23,
gender: “male”,
},
reducers: {
newUser: (state, { payload }) => {
state.fullname = payload.fullname;
state.age = payload.age;
state.gender = payload.gender;
},
removeUser(state) {
state.fullname = “”;
state.age = 0;
state.gender = “”;
},
},
});
const persistConfig = {
key: “user”,
storage,
version: 1,
};
export const userReducer = persistReducer(persistConfig, userSlice.reducer);
export default userSlice;
————————————————————————————–
// reducer file (store root directory) => reducer.ts
import { combineReducers } from “@reduxjs/toolkit”;
import { userReducer } from “./features/user”;
import { authReducer } from “./features/auth”;
const rootReducer = combineReducers({
auth: authReducer,
user: userReducer,
});
//? by bottom code you can persist all reducers
// import storage from “redux-persist/lib/storage”;
// import { persistReducer } from “redux-persist”;
// const persistConfig = {
// key: “all”,
// storage,
// version: 1,
// };
// export default persistReducer(persistConfig, rootReducer);
export default rootReducer;
————————————————————————————–
// store file (store root directory) => store.ts
import { configureStore } from “@reduxjs/toolkit”;
import { persistStore } from “redux-persist”;
import rootReducer from “./reducer”;
// Create the store
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
// Persist the store for redux persist
export const persistor = persistStore(store);
// Get the State type
export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;
export default store;
How to use the persisted state to render the data