How to Persist Redux State in React Applications
To persist
Redux state, use the redux-persist library which saves the state to storage like localStorage. It automatically rehydrates the state on app reload, keeping your data intact between sessions.Syntax
Use redux-persist by wrapping your Redux store with persistStore and persistReducer. Configure a persistConfig to specify storage and which parts of the state to save.
persistConfig: Defines storage and keys.persistReducer: Enhances your root reducer to handle persistence.persistStore: Creates a persistor to control persistence lifecycle.
javascript
import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; import { configureStore } from '@reduxjs/toolkit'; import rootReducer from './reducers'; const persistConfig = { key: 'root', storage, whitelist: ['someReducer'] // optional: which reducers to persist }; const persistedReducer = persistReducer(persistConfig, rootReducer); const store = configureStore({ reducer: persistedReducer }); const persistor = persistStore(store); export { store, persistor };
Example
This example shows a simple React app with Redux state persisted using redux-persist. The counter value stays saved in localStorage even after refreshing the page.
javascript
import React from 'react'; import { Provider, useDispatch, useSelector } from 'react-redux'; import { configureStore, createSlice } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; import { PersistGate } from 'redux-persist/integration/react'; // Slice with counter state const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: state => { state.value += 1; }, decrement: state => { state.value -= 1; } } }); const persistConfig = { key: 'root', storage }; const persistedReducer = persistReducer(persistConfig, counterSlice.reducer); const store = configureStore({ reducer: persistedReducer }); const persistor = persistStore(store); function Counter() { const count = useSelector(state => state.value); const dispatch = useDispatch(); return ( <div> <h1>Count: {count}</h1> <button onClick={() => dispatch(counterSlice.actions.increment())}>+</button> <button onClick={() => dispatch(counterSlice.actions.decrement())}>-</button> </div> ); } export default function App() { return ( <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <Counter /> </PersistGate> </Provider> ); }
Output
Count: 0 (initially)
Click + button: Count increments
Refresh page: Count value remains the same
Common Pitfalls
- Not wrapping the app with
PersistGatecauses UI to render before state rehydration, leading to flicker or wrong initial state. - Forgetting to specify storage in
persistConfigdefaults to localStorage; using sessionStorage requires explicit import. - Persisting large or sensitive data can slow app or cause security issues; whitelist only needed reducers.
- Not handling migrations when state shape changes can cause errors; use
redux-persistversioning features.
jsx
/* Wrong: Missing PersistGate causes UI to show before state loads */ <Provider store={store}> <Counter /> </Provider> /* Right: Wrap with PersistGate to delay rendering until rehydration */ <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <Counter /> </PersistGate> </Provider>
Quick Reference
| Concept | Description |
|---|---|
| persistConfig | Settings for persistence like storage and keys to save |
| persistReducer | Wraps root reducer to add persistence logic |
| persistStore | Creates persistor to manage saving and loading state |
| PersistGate | Delays UI rendering until persisted state is loaded |
| storage | Storage engine, usually localStorage or sessionStorage |
Key Takeaways
Use redux-persist to save and rehydrate Redux state automatically.
Wrap your root reducer with persistReducer and create a persistor with persistStore.
Wrap your app in PersistGate to delay rendering until state is loaded.
Whitelist only necessary reducers to avoid saving unwanted data.
Test persistence by refreshing the app and verifying state remains.