How to setup Redux Toolkit with Redux Persist in NextJS App Router

ScreenRecording2024-01-28at03 33 04-ezgif com-video-to-gif-converter

Install dependencies

yarn add @reduxjs/toolkit react-redux redux-persist

Create a slice

import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";

export interface IAuthState {
  authState: boolean;
}

const initialState: IAuthState = {
  authState: false,
};

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    setAuthState: (state, action: PayloadAction<boolean>) => {
      state.authState = action.payload;
    },
  },
});

export const { setAuthState } = authSlice.actions;
export const authReducer = authSlice.reducer;

Create the store

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { useDispatch, TypedUseSelectorHook, useSelector } from "react-redux";
import { persistReducer } from "redux-persist";
import { authReducer } from "@/store/authSlice";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";

const createNoopStorage = () => {
  return {
    getItem() {
      return Promise.resolve(null);
    },
    setItem(_key: string, value: number) {
      return Promise.resolve(value);
    },
    removeItem() {
      return Promise.resolve();
    },
  };
};

const storage =
  typeof window !== "undefined"
    ? createWebStorage("local")
    : createNoopStorage();

const authPersistConfig = {
  key: "auth",
  storage: storage,
  whitelist: ["authState"],
};

const persistedReducer = persistReducer(authPersistConfig, authReducer);

const rootReducer = combineReducers({
  auth: persistedReducer,
});

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

Create the provider

"use client";

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

persistStore(store);
export default function ReduxProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return <Provider store={store}>{children}</Provider>;
}

Then wrap the app with the provider

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import dynamic from "next/dynamic";

const ReduxProvider = dynamic(() => import("@/store/redux-provider"), {
  ssr: false
});

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

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

Example of Server and Client Redering

import AuthUpdater from "./auth-updater";
import AuthViewer from "./auth-viewer";

export default async function Home() {

  console.log("serverside rendering");

  return (
    <main className="w-full h-screen grid md:grid-cols-2 place-items-center">
      <AuthUpdater />
      <AuthViewer />
    </main>
  );
}

Access the store

"use client";

import React from "react";
import { useAppSelector } from "@/store";

const AuthViewer = () => {
  const authState = useAppSelector((state) => state.auth.authState);

  return (
    <div className="flex gap border border-1 border-black p-20">
      You are now {authState ? "Logged  In" : "Logged Out"}
    </div>
  );
};

export default AuthViewer;

Update the store

"use client";

import React from "react";
import { setAuthState } from "@/store/authSlice";
import { useAppDispatch } from "@/store";

const AuthUpdater = () => {
  const dispatch = useAppDispatch();

  return (
    <div className="flex gap-4 border border-1 border-black p-20">
      <button
        className="p-4 border border-1 border-black hover:bg-gray-300"
        onClick={() => dispatch(setAuthState(true))}
      >
        Log in
      </button>
      <button
        className="p-4 border border-1 border-black hover:bg-gray-300"
        onClick={() => dispatch(setAuthState(false))}
      >
        Log out
      </button>
    </div>
  );
};

export default AuthUpdater;

That's it. Now you can use redux tooling with redux persist in NextJS