59

I have configured redux-persist with a traditional react-redux setup like this:

onst persistConfig = {
  key: 'root',
  storage,
  whitelist: ['todos'],
};

const persistedReducer = persistReducer(persistConfig, reducer);

const store = createStore(
  persistedReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

const persistor = persistStore(store);

// wrapper
const StateProvider = ({ children }) => {
  return (
    <Provider store={store}>
      <PersistGate loading={<div>Loading...</div>} persistor={persistor}>
        {children}
      </PersistGate>
    </Provider>
  );
};

But, how can I configure it with redux-toolkit? So far I have tried this:

const persistedReducer = persistReducer(persistConfig, todoreducer);
const store = configureStore({
  reducer: {
    todos: persistedReducer,
  },
});

const persistor = persistStore(store);

// wrapper
const StateProvider = ({ children }) => {
  return (
    <Provider store={store}>
      <PersistGate loading={<div>Loading...</div>} persistor={persistor}>
        {children}
      </PersistGate>
    </Provider>
  );
};

But, It is not working. I can't get the todos by const todos = useSelector(state => state.todos);

it returns undefined.

Ashik
  • 2,888
  • 8
  • 28
  • 53

5 Answers5

97

store.js

import {configureStore} from '@reduxjs/toolkit';
import storage from 'redux-persist/lib/storage'
import {combineReducers} from "redux"; 
import { persistReducer } from 'redux-persist'
import thunk from 'redux-thunk'

const reducers = combineReducers({
 //...            
});

const persistConfig = {
    key: 'root',
    storage
};

const persistedReducer = persistReducer(persistConfig, reducers);


const store = configureStore({
    reducer: persistedReducer,
    devTools: process.env.NODE_ENV !== 'production',
    middleware: [thunk]
});

export default store;

Index/App.js

import store from './app/store';
import { PersistGate } from 'redux-persist/integration/react'
import { persistStore } from 'redux-persist'

let persistor = persistStore(store);

    <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
        <App/>
        </PersistGate>
    </Provider>,
Manuj Kathuria
  • 1,043
  • 9
  • 13
32

There's now a guide in the Redux Toolkit docs that will help you with this, as well as providing advice for purging the persisted state and working with RTK Query.

It also has some code you'll want to copy and paste to ignore the action types react-persist dispatches. This will stop it throwing error messages (I was getting the error A non-serializable value was detected in an action, in the path: `register`. when I ran my React Native app with Expo).

At time of writing, their code sample looks like this:

import { configureStore } from '@reduxjs/toolkit'
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { PersistGate } from 'redux-persist/integration/react'

import App from './App'
import rootReducer from './reducers'

const persistConfig = {
  key: 'root',
  version: 1,
  storage,
}

const persistedReducer = persistReducer(persistConfig, rootReducer)

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

let persistor = persistStore(store)

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>,
  document.getElementById('root')
)
pipedreambomb
  • 3,711
  • 1
  • 22
  • 18
5

in store.js

import { configureStore } from "@reduxjs/toolkit";
import storage from "redux-persist/lib/storage";
import { persistReducer } from "redux-persist";
import { combineReducers } from "redux";

const persistConfig = {
  key: "root",
  version: 1,
  storage,
  // if you do not want to persist this part of the state
  blacklist: ['omitedPart']
};

const reducer = combineReducers({
  cart: CartReducer,
  // not persisting this reducer
  omitedPart:OmitReducer
});
// this ensures your redux state is saved to persisted storage whenever it changes
// we pass this to the store
const persistedReducer = persistReducer(persistConfig, reducer);

const store = configureStore({
  reducer: persistedReducer,
});

export default store;

in your root component, index.js

import { Provider } from "react-redux";
import store from "./redux/store";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";

let persistor = persistStore(store);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      //PersistGate delays the rendering of UI until the persisted state has been retrrieved and saved to redux
      <PersistGate persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • Best solution. For Next JS just add redux thunk... ''export const store = configureStore({ reducer: persistedReducer, middleware: [thunk] });'' – xreyc Oct 22 '22 at 14:42
0

When using TypeScript with stateReconciles option from persistConfig, you need a little type shim to avoid

  • a) unknown generic inference
persistReducer<unknown, AnyAction>

Argument of type 'Reducer<CombinedState<{ ... }>, AnyAction>' is not assignable to parameter of type 'Reducer<unknown, AnyAction>'.
  Types of parameters 'state' and 'state' are incompatible.
    Type 'unknown' is not assignable to type 'CombinedState<{ ... }>'.ts(2345)

  • b) circular type reference
const persistConfig = {
  stateReconciles: hardSet as RootState,
}

export type RootState = ReturnType<typeof store.getState> // Type alias 'RootState' circularly references itself.

The solution is in the extracted type CombinedState, as follows:

import type { Reducer } from '@reduxjs/toolkit'
import { configureStore, combineReducers } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import * as rp from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import hardSet from 'redux-persist/lib/stateReconciler/hardSet'

import createCollectionForm from './slices/CollectionFormSlice'
import auth from './slices/AuthSlice'

const persistConfig = {
  key: 'root',
  storage,
  stateReconciles: hardSet as (inboundState: CombinedState) => CombinedState,
  version: 1,
}

type CombinedState = typeof rootReducer extends Reducer<infer U, any> ? U : never

const rootReducer = combineReducers({
  auth,
  createCollectionForm,
})

const persistedReducer = persistReducer(persistConfig, rootReducer)

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

export const persistor = persistStore(store)


export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Qwerty
  • 29,062
  • 22
  • 108
  • 136
-4

I just tried this with the exact similar setup and it seemed to work. If it's helpful, I'm on "@reduxjs/toolkit": "^1.4.0",, "redux-persist": "^6.0.0", and "react-redux": "^7.2.1". HTH.

omarish
  • 575
  • 2
  • 7
  • 15