How to use Redux-Persist with Next.js App Router

638 Views Asked by At

I followed the official docs in how to make the redux work with Next.js app router. Everything worked as expected, but now I am trying to persist the data with redux-persist and things got very muddy.

There is no example in the official redux docs. I don't know how to deal with the fact that I am creating a Redux store per request by using configureStore wrapped in a makeStore function (recommended approach by official Redux docs to use Redux with Next app router).

I can't even pass makeStore to the persistStore method without getting type errors:

Argument of type '() => EnhancedStore<{ user:
userInterface; } & PersistPartial, UnknownAction,
Tuple<[StoreEnhancer<{ dispatch: ThunkDispatch<{ user: userInterface;
} & PersistPartial, undefined, UnknownAction>; }>, StoreEnhancer]>>'
is not assignable to parameter of type 'Store<any, UnknownAction,
unknown>'

This is where I am currently at:

store.js:

import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { configureStore, combineReducers } from '@reduxjs/toolkit'
import {userReducer} from "./user/userSlice"

const combinedReducers = combineReducers({
  user: userReducer,
})

const persistedReducer = persistReducer(
  {
      key: 'root',
      storage,
      whitelist: ['user'],
  },
  combinedReducers
)

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

export const persistor = persistStore(makeStore)

export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']

store provider:

"use client";

import { useRef } from "react";
import { Provider } from "react-redux";
import { makeStore, AppStore } from "../redux/store";

export default function StoreProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const storeRef = useRef<AppStore>();
  if (!storeRef.current) {
    // Create the store instance the first time this renders
    storeRef.current = makeStore();
  }

  return <Provider store={storeRef.current}>{children}</Provider>;
}

layout.js

import type { Metadata } from "next";
import "./globals.css";
import StoreProvider from "./StoreProvider";

export const metadata: Metadata = {
  title: "App",
  description: "App Desription",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <StoreProvider>{children}</StoreProvider>
      </body>
    </html>
  );
}

How can I adapt the code to persist the Redux data considering the necessity of creating the store per request?

1

There are 1 best solutions below

1
Burhan On

Below is my implentation of the store.ts

import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

import userSlice from './features/login/userSlice'

const persistConfig = {
  key: 'persist',
  storage,
}

const rootReducer = combineReducers({
  user: userSlice,
})

const makeConfiguredStore = () =>
  configureStore({
    reducer: rootReducer,
  })

export const makeStore = () => {
  const isServer = typeof window === 'undefined'
  if (isServer) {
    return makeConfiguredStore()
  } else {
    const persistedReducer = persistReducer(persistConfig, rootReducer)
    let store: any = configureStore({
      reducer: persistedReducer,
    })
    store.__persistor = persistStore(store)
    return store
  }
}

export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']

We need to know of the server and client mode in Next.js because we don't eventually need persistence on the server side.

Below is my implementation of StoreProvider.tsx

'use client'

import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '../lib/store'
import { PersistGate } from 'redux-persist/integration/react'

export default function StoreProvider({
  children,
}: {
  children: React.ReactNode
}) {
  const storeRef = useRef<AppStore>()
  if (!storeRef.current) {
    // Create the store instance the first time this renders
    storeRef.current = makeStore()
  }

  return (
    <Provider store={storeRef.current}>
      <PersistGate loading={null} persistor={storeRef.current.__persistor}>
        {children}
      </PersistGate>
    </Provider>
  )
}

Using ref to maintain the store state between renders, you can then wrap your whole app with this StateProvider component.

A huge shout-out to this blog here for making it look easier.