I am using NextJS 14 with App router with Spring Boot in the backend. I have public and private routes. For the private routes I have a login page. When the user logs in successfully I return a JWT token as a cookie.
This is my root layout
import "./global.css";
import Header from "./components/header/header";
import {GlobalProvider} from "./_lib/global-provider";
import MainContent from "./components/main-content";
export default async function RootLayout({children}) {
return (
<html lang="en" className="">
<body className="min-h-screen scroll-smooth">
<div className="flex flex-col min-h-screen">
<GlobalProvider>
<Header/>
<MainContent>
{children}
</MainContent>
</GlobalProvider>
</div>
</body>
<GoogleAnalytics/>
</html>
);
}
And this is my Header component that should show a Log Out button when the user is logged in.
"use client";
import Link from "next/link";
import {useAdminAuthContext} from "../../_lib/admin-auth-provider";
import LogoHeader from "./logo-header";
export default function Header() {
const adminAuth = useAdminAuthContext();
return (
<header className="sticky top-0 z-50 flex-none bg-gray-50 py-6 drop-shadow">
<nav className="relative mx-auto flex justify-between px-8 max-sm:px-2">
<div className="flex items-center ">
<LogoHeader/>
</div>
<div className="flex items-center">
{
adminAuth.user != null && <button>LOG OUT</button>
}
</div>
</nav>
</header>
);
}
I am using an AuthContext that exposes logIn and logOut functions that actually calls my backend and saves into a state prop if the user is authenticated.
The problem is that when the page is reloaded, the state is lost and the LOG OUT button disappears.
"use client";
import {createContext, useContext, useEffect, useState} from "react";
import {useRouter} from 'next/navigation';
const host = process.env.NEXT_PUBLIC_API_HOST;
export const AdminAuthContext = createContext({});
export const AdminAuthProvider = ({children}) => {
const router = useRouter(); // Initialize useRouter
const [user, setUser] = useState(null); // State to store admin user details
const doAdminLogin = async (email, password) => {
const request = await fetch(`${host}/api/admin/login`, {
cache: "no-store",
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({email, password}),
});
if (!request.ok) {
console.error('Login failed');
throw new Error("Error logging in");
}
const res = await request.json();
setUser(res.username);
router.push('/private/admin/dashboard'); // Use router.push to navigate
};
const doAdminLogout = async () => {
const request = await fetch(`${host}/api/admin/logout`, {
cache: "no-store",
method: "POST",
credentials: "include",
});
};
return (
<AdminAuthContext.Provider value={{user, doAdminLogin, doAdminLogout}}>
{children}
</AdminAuthContext.Provider>
);
};
export function useAdminAuthContext() {
return useContext(AdminAuthContext);
}
So the question is: What do I need to change so my component shows the LOG OUT button when the user is logged in?
2
Answers
You’re always going to lose component level state on page navigation. Redux internally attaches things to the window object to avoid this. I’d recommend either using redux, maybe even with a persistant storage plugin if you want the storage to persist even further, but redux alone should handle this for you without your current issue.
When you refresh the page, any state is lost. To remember something from a previous session, you need to use one of the browser storage (
cookies
,localStorage
…)In your case, since a cookie is registered after login, you can simply make a call on load to your API which validate the cookie and send you the user, like so: