skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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:

    "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 [loading, setLoading] = useState(true);
    
      useEffect(() => {
        /* 
          Use the right API endpoint to get the user. If there is a cookie, 
          from a previous login, you should get the user 
          as when you just logged in
       */
        const checkAdminLogin = async () => {
          try {
            const request = await fetch(`${host}/api/admin/user`, {
              method: "GET",
              credentials: "include",
            });
    
            if (request.ok) {
              const res = await request.json();
              setUser(res.username);
            } else {
              throw new Error("Not authorized");
            }
          } catch (error) {
            console.error(error);
            router.push("/admin/login");
          } finally {
            setLoading(false);
          }
        };
        checkAdminLogin();
      }, [router]);
    
      const doAdminLogin = async (email, password) => {
        // ...
      };
    
      const doAdminLogout = async () => {
        // ...
      };
    
      return (
        <AdminAuthContext.Provider value={{ user, doAdminLogin, doAdminLogout }}>
          {loading ? "Loading..." : children}
        </AdminAuthContext.Provider>
      );
    };
    
    export function useAdminAuthContext() {
      return useContext(AdminAuthContext);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search