yarn add @reduxjs/toolkit react-redux redux-persist
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;
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;
"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>
);
}
"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;
"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