Let's dive right into the code :
"use client";
import { useState, useEffect, useRef } from "react";
import LoginClient from "../LearnerRegFlow/LoginClient";
import SignupClient from "../LearnerRegFlow/SignupClient";
import ResetPassword from "../LearnerRegFlow/ResetPassword";
import Link from "next/link";
import Image from "next/image";
import { LogIn, ChevronDown } from "lucide-react";
const Navbar = () => {
const [showLogin, setShowLogin] = useState(false);
const [showSignup, setShowSignup] = useState(false);
const [showResetPassword, setShowResetPassword] = useState(false);
const [user, setUser] = useState<{ name: string; email: string } | null>(null);
const [dropdownOpen, setDropdownOpen] = useState(false);
useEffect(() => {
// Load user from localStorage
const storedUser = localStorage.getItem("user");
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
const handleCloseModals = () => {
setShowLogin(false);
setShowSignup(false);
setShowResetPassword(false);
};
const handleForgotPassword = () => {
setShowLogin(false);
setShowSignup(false);
setShowResetPassword(true);
};
const handleSwitchToSignup = () => {
setShowLogin(false);
setShowResetPassword(false);
setShowSignup(true);
};
const handleSwitchToLogin = () => {
setShowSignup(false);
setShowResetPassword(false);
setShowLogin(true);
};
const handleLoginSuccess = (userData: { name: string; email: string }) => {
setUser(userData);
};
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
setUser(null);
setDropdownOpen(false);
};
return (
<nav className="flex justify-between items-center px-xl py-4 bg-white font-sans mx-auto rounded box-border width-full">
<div className="flex items-center gap-6 space-y-2 m-2">
<Link href="#" className="link">
<Image className="max-h-8" src="/Azubi-Logo.svg" alt="logo" width={100} height={100} />
</Link>
<Link href="/" className="link m-0 block pb-2 text-black text-[16px] font-inter">Home</Link>
<Link href="/courses" className="link m-0 block pb-2 text-black text-[16px] font-inter">Courses</Link>
</div>
<div className="relative">
{user ? (
// Logged-in UI (Profile Dropdown)
<div className="relative flex items-center cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
<div className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
{user?.name
? user.name
.split(" ") // Split by space into words
.map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word
.slice(0, 2) // Take only first two words (if available)
.join("") // Combine letters
: "U"}
</div>
<span className="text-black font-medium">{user.name}</span>
<ChevronDown className="text-black ml-[48px]" />
{/* Dropdown Menu */}
{dropdownOpen && (
<div className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20">
<nav className="w-full text-left px-4 py-2 hover:bg-white text-black">
<Link href="/" className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200">Portal</Link>
<Link
href="/"
onClick={handleLogout}
className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200"
>
Logout
</Link>
</nav>
</div>
)}
</div>
) : (
// Login Button
<button
className="link bg-transparent text-blue-700 py-3 px-6 border border-blue-700 rounded-md flex items-center gap-3 text-base font-medium transition-colors duration-300 ease-in-out hover:bg-hero-bg hover:text-white font-inter"
onClick={() => setShowLogin(true)}
>
<span className="font-inter">Login</span>
<LogIn />
</button>
)}
{/* LOGIN / SIGNUP / RESET PASSWORD MODALS */}
{showLogin || showSignup || showResetPassword ? (
<div className="absolute top-[70px] right-[0px] z-10">
{showLogin && (
<LoginClient
onClose={handleCloseModals}
onForgotPassword={handleForgotPassword}
onSignup={handleSwitchToSignup}
onLoginSuccess={handleLoginSuccess}
/>
)}
{showSignup && (
<SignupClient
onClose={handleCloseModals}
onLogin={handleSwitchToLogin}
/>
)}
{showResetPassword && (
<ResetPassword onClose={handleCloseModals} onSignup={handleSwitchToSignup} />
)}
</div>
) : null}
</div>
</nav>
);
};
export default Navbar;
I know this is a lot to wrap your head around but stay with me or it's not maybe it's part of the day-to-day life of a developer to go through this amount of code. Well, this is my thinking process when building the Navbar components.
This is the relative path to the component src\app\components\LearnerPage\Navbar.tsx. you can get my folder structure for my project in the previous post, ie Update 2
This file defines a navigation bar component for my Next.js application, handling user authentication and navigation.
Here's a detailed explanation:
Navbar Component
1. "use client";
This directive at the beginning of the file indicates that this component is a client-side component. In Next.js, components are server-side rendered by default. "use client"
specifies that this component, and any components imported into it, will be rendered in the user's browser. This is necessary because this component uses React hooks like useState
and useEffect
, which are client-side functionalities.
- Server-Side Rendering (SSR): The server sends a complete web page to your browser, so it loads quickly and is easier for search engines to understand.
- Client-Side Rendering (CSR): The server sends basic instructions to your browser, which then builds the web page itself. This can make the first load slower, but moving between pages can be faster after that.
React hooks (e.g., useState
, useEffect
) are tools that manage things like data changes and side effects, and they work only in the browser environment.
2. Import Statements
import { useState, useEffect, useRef } from "react";
import LoginClient from "../LearnerRegFlow/LoginClient";
import SignupClient from "../LearnerRegFlow/SignupClient";
import ResetPassword from "../LearnerRegFlow/ResetPassword";
import Link from "next/link";
import Image from "next/image";
import { LogIn, ChevronDown } from "lucide-react";
typescript
useState, useEffect, useRef from "react": These are React hooks:
useState: Allows you to add state variables to functional components. State variables are used to manage data that can change and trigger re-renders of the component when they do.
- State Variables: These are like special containers for data in your component. They can hold any kind of data, such as numbers, text, or objects.
- Manage Data Changes: When the data inside these state variables changes, React knows something has changed in the component.
- Trigger Re-renders: When the data changes, React automatically updates the component to show the new data. This process is called re-rendering.
useEffect: Lets you perform side effects in functional components. Side effects are operations that interact with the outside world, like data fetching, DOM manipulation, or timers. In this case, it's used to load user data from local storage.
useRef: While imported, useRef is not actually used in the provided code snippet. It's typically used to directly interact with DOM elements or to hold mutable values that don't trigger re-renders.
- Interacting with DOM Elements: It allows you to directly interact with elements on the page (like inputs, buttons, etc.) without causing the component to re-render. This is useful for things like focusing an input or measuring an element's size.
- Holding Mutable Values: You can use useRef to store values that might change but don’t need to trigger a re-render. For example, you might use it to keep track of a timer, or to store a previous value for comparison.
LoginClient from "../LearnerRegFlow/LoginClient": Imports a component named LoginClient. This component is responsible for rendering the login modal or form. It's located in the ../LearnerRegFlow/LoginClient path, a directory structure within the Next.js project.
SignupClient from "../LearnerRegFlow/SignupClient": Imports a component named SignupClient, for the signup modal or form, located in the same directory.
ResetPassword from "../LearnerRegFlow/ResetPassword": Imports the ResetPassword component, for handling the password reset functionality, also from the same directory.
Link from "next/link": Imports the Link component from Next.js. This component is used for client-side navigation between routes in your Next.js application, providing better performance than traditional tags.
Image from "next/image": Imports the Image component from Next.js. This component is used for optimized image rendering, including features like lazy loading and responsive sizing.
{ LogIn, ChevronDown } from "lucide-react": Imports two icons, LogIn and ChevronDown, from the lucide-react icon library. These are likely used for visual elements in the navigation bar.
3. Component Definition:
const Navbar = () => { ... }
This line defines a functional React component named Navbar. Functional components are a common way to create UI elements in React.
4. State Variables:
const [showLogin, setShowLogin] = useState(false);
const [showSignup, setShowSignup] = useState(false);
const [showResetPassword, setShowResetPassword] = useState(false);
const [user, setUser] = useState<{ name: string; email: string } | null>(null);
const [dropdownOpen, setDropdownOpen] = useState(false);
-
showLogin, setShowLogin: A state variable to control the visibility of the login modal.
showLogin
holds a boolean value (initially false), andsetShowLogin
is a function to update this value. WhenshowLogin
is true, the login modal will be displayed. -
showSignup, setShowSignup: Similar to
showLogin
, this controls the visibility of the signup modal. - showResetPassword, setShowResetPassword: Controls the visibility of the reset password modal.
-
user, setUser: Stores the logged-in user's information. The type
useState<{ name: string; email: string } | null>(null)
specifies thatuser
can hold either an object with name and email properties (both strings) or null (if no user is logged in). It's initialized to null. - dropdownOpen, setDropdownOpen: Controls whether the user profile dropdown menu is open or closed. It's a boolean state, initially false.
5. useEffect Hook:
useEffect(() => {
// Load user from localStorage
const storedUser = localStorage.getItem("user");
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
- Runs once after the component mounts (because of the empty dependency array
[]
). -
localStorage.getItem("user")
: Retrieves user data from the browser's localStorage using the key "user". -
if (storedUser)
: Checks if user data was found in localStorage. -
setUser(JSON.parse(storedUser))
: Converts stored JSON string into an object and updatesuser
state.
6. Modal Handlers:
const handleCloseModals = () => {
setShowLogin(false);
setShowSignup(false);
setShowResetPassword(false);
};
const handleForgotPassword = () => {
setShowLogin(false);
setShowSignup(false);
setShowResetPassword(true);
};
const handleSwitchToSignup = () => {
setShowLogin(false);
setShowResetPassword(false);
setShowSignup(true);
};
const handleSwitchToLogin = () => {
setShowSignup(false);
setShowResetPassword(false);
setShowLogin(true);
};
- handleCloseModals: Closes all modals (login, signup, reset password).
- handleForgotPassword: Closes login and signup modals and opens the reset password modal.
- handleSwitchToSignup: Closes login and reset password modals and opens the signup modal.
- handleSwitchToLogin: Closes signup and reset password modals and opens the login modal.
7. Authentication Handlers:
const handleLoginSuccess = (userData: { name: string; email: string }) => {
setUser(userData);
};
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
setUser(null);
setDropdownOpen(false);
};
-
handleLoginSuccess(userData): Updates
user
state with received user data (name and email). -
handleLogout():
- Removes "token" and "user" from localStorage.
- Sets
user
state back to null. - Closes the dropdown menu.
8. JSX Structure (the return statement):
return (
<nav className="flex justify-between items-center px-xl py-4 bg-white font-sans mx-auto rounded box-border width-full">
<div className="flex items-center gap-6 space-y-2 m-2">
<Link href="#" className="link">
<Image className="max-h-8" src="/Azubi-Logo.svg" alt="logo" width={100} height={100} />
</Link>
<Link href="/" className="link m-0 block pb-2 text-black text-[16px] font-inter">Home</Link>
<Link href="/courses" className="link m-0 block pb-2 text-black text-[16px] font-inter">Courses</Link>
</div>
<div className="relative">
{user ? (
// Logged-in UI (Profile Dropdown)
<div className="relative flex items-center cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
<div className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
{user?.name
? user.name
.split(" ") // Split by space into words
.map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word
.slice(0, 2) // Take only first two words (if available)
.join("") // Combine letters
: "U"}
</div>
<span className="text-black font-medium">{user.name}</span>
<ChevronDown className="text-black ml-[48px]" />
{/* Dropdown Menu */}
{dropdownOpen && (
<div className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20">
<nav className="w-full text-left px-4 py-2 hover:bg-white text-black">
<Link href="/" className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200">Portal</Link>
<Link
href="/"
onClick={handleLogout}
className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200"
>
Logout
</Link>
</nav>
</div>
)}
</div>
) : (
// Login Button
<button
className="link bg-transparent text-blue-700 py-3 px-6 border border-blue-700 rounded-md flex items-center gap-3 text-base font-medium transition-colors duration-300 ease-in-out hover:bg-hero-bg hover:text-white font-inter"
onClick={() => setShowLogin(true)}
>
<span className="font-inter">Login</span>
<LogIn />
</button>
)}
{/* LOGIN / SIGNUP / RESET PASSWORD MODALS */}
{showLogin || showSignup || showResetPassword ? (
<div className="absolute top-[70px] right-[0px] z-10">
{showLogin && (
<LoginClient
onClose={handleCloseModals}
onForgotPassword={handleForgotPassword}
onSignup={handleSwitchToSignup}
onLoginSuccess={handleLoginSuccess}
/>
)}
{showSignup && (
<SignupClient
onClose={handleCloseModals}
onLogin={handleSwitchToLogin}
/>
)}
{showResetPassword && (
<ResetPassword onClose={handleCloseModals} onSignup={handleSwitchToSignup} />
)}
</div>
) : null}
</div>
</nav>
);
};
export default Navbar;
<nav>
Element
The root element represents the navigation bar. It uses Tailwind CSS classes for styling (e.g., flex
, justify-between
, items-center
, bg-white
).
First <div>
(Logo and Nav Links)
<div className="flex items-center gap-6 space-y-2 m-2">
<Link href="#"> ... </Link>
<Image ... src="/Azubi-Logo.svg" ... />
<Link href="/">Home</Link>
<Link href="/courses">Courses</Link>
</div>
Explanation:
-
className="flex items-center gap-6 space-y-2 m-2"
: Uses flexbox to arrange items horizontally, with spacing and margins. -
<Link href="#"> ... </Link>
: A Next.jsLink
component for the logo.href="#"
might be a placeholder, and you might want to change it to the homepage path (/
). -
<Image ... src="/Azubi-Logo.svg" ... />
: Displays the logo image using the Next.jsImage
component, referencing an image file in thepublic
directory (/Azubi-Logo.svg
). - Two more
<Link>
components for "Home" and "Courses" navigation links, pointing to the root path (/
) and/courses
path, respectively.
Second <div>
(User Authentication and Dropdown)
<div className="relative">
{user ? (
<div className="relative flex items-center cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
<div className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
{user?.name
? user.name
.split(" ")
.map((word) => word.charAt(0).toUpperCase())
.slice(0, 2)
.join("")
: "U"}
</div>
<span className="text-black font-medium">{user.name}</span>
<ChevronDown className="text-black ml-[48px]" />
</div>
) : (
<button className="link bg-transparent" onClick={() => setShowLogin(true)}>
<span className="font-inter">Login</span>
<LogIn />
</button>
)}
</div>
Explanation:
-
className="relative"
: Sets the positioning context for absolute positioning of the dropdown menu later. -
Conditional Rendering (
{user ? (...) : (...) }
):- If a user is logged in (
user
is truthy), it displays the logged-in UI. - If no user is logged in, it displays the logged-out UI.
- If a user is logged in (
Logged-in UI (user ? (...)
):
-
<div className="relative flex items-center cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
: A clickablediv
that toggles thedropdownOpen
state when clicked, opening or closing the dropdown menu. -
Profile Icon
<div>
:-
className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold"
: Styles the profile icon container (circular, colored background, etc.). - User Initial Logic:
{user?.name ? user.name .split(" ") // Split by space into words .map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word .slice(0, 2) // Take only first two words (if available) .join("") // Combine letters : "U"}
This code extracts the first letter of the first two words of the user's name to display as initials in the profile icon. If there's no name, it defaults to
"U"
. -
<span className="text-black font-medium">{user.name}</span>
: Displays the user's name next to the icon.<ChevronDown className="text-black ml-[48px]" />
: Displays a chevron down icon, indicating a dropdown menu.
Logged-out UI (: (...)
):
-
<button ... onClick={() => setShowLogin(true)}>
: A button element styled as a link.-
className="link bg-transparent ..."
: Tailwind CSS classes for styling the button. -
onClick={() => setShowLogin(true)}
: When clicked, it setsshowLogin
totrue
, opening the login modal. -
<span className="font-inter">Login</span>
: Text "Login" for the button. -
<LogIn />
: TheLogIn
icon fromlucide-react
.
-
Dropdown Menu ({dropdownOpen && (...)}
)
{dropdownOpen && (
<div className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20">
<nav className="...">
<Link href="/">Portal</Link>
<Link href="/" onClick={handleLogout}>Logout</Link>
</nav>
</div>
)}
Explanation:
-
Conditionally rendered only when
dropdownOpen
istrue
. -
className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20"
: Styles and positions the dropdown menu absolutely, appearing below and to the right of the profile icon. -
<nav className="..."> ... </nav>
: Contains the dropdown menu items.-
<Link href="/">Portal</Link>
: A link to a "Portal" page. -
<Link href="/" onClick={handleLogout}>Logout</Link>
: A link that, when clicked, calls thehandleLogout
function to log the user out.
-
Modal Rendering ({showLogin || showSignup || showResetPassword ? (...) : null}
)
{showLogin || showSignup || showResetPassword ? (
<div className="absolute top-[70px] right-[0px] z-10">
{showLogin && <LoginClient onClose={handleCloseModals} onForgotPassword={handleForgotPassword} onSignup={handleSwitchToSignup} onLoginSuccess={handleLoginSuccess} />}
{showSignup && <SignupClient onClose={handleCloseModals} onLogin={handleSwitchToLogin} />}
{showResetPassword && <ResetPassword onClose={handleCloseModals} onSignup={handleSwitchToSignup} />}
</div>
) : null}
Explanation:
-
Conditionally renders the modal components only if
showLogin
,showSignup
, orshowResetPassword
istrue
. -
className="absolute top-[70px] right-[0px] z-10"
: Styles and positions the modal container absolutely, appearing below the navigation bar on the right side. -
{showLogin && <LoginClient ... />}
: Renders theLoginClient
component ifshowLogin
istrue
.-
onClose={handleCloseModals}
: Passes thehandleCloseModals
function as a prop toLoginClient
, allowing the login modal to be closed from withinLoginClient
. -
onForgotPassword={handleForgotPassword}
: PasseshandleForgotPassword
to allow the login modal to trigger the reset password modal. -
onSignup={handleSwitchToSignup}
: PasseshandleSwitchToSignup
to allow switching to the signup modal from the login modal. -
onLoginSuccess={handleLoginSuccess}
: PasseshandleLoginSuccess
to be called when login is successful, updating theuser
state in theNavbar
.
-
-
{showSignup && <SignupClient ... />}
: Renders theSignupClient
component ifshowSignup
istrue
.-
onClose={handleCloseModals}
: PasseshandleCloseModals
to close the signup modal. -
onLogin={handleSwitchToLogin}
: PasseshandleSwitchToLogin
to allow switching to the login modal from the signup modal.
-
-
{showResetPassword && <ResetPassword ... />}
: Renders theResetPassword
component ifshowResetPassword
istrue
.-
onClose={handleCloseModals}
: PasseshandleCloseModals
to close the reset password modal. -
onSignup={handleSwitchToSignup}
: PasseshandleSwitchToSignup
to allow switching to the signup modal from the reset password modal.
-
Conclusion
Building a Navbar component in a Next.js application might seem like a daunting task at first, especially when you’re juggling multiple states, modals, and user authentication flows. However, breaking it down into smaller, manageable pieces—like handling state, rendering conditional UI, and managing user interactions—makes the process much more approachable.
This Navbar component is a great example of how to create a dynamic and user-friendly navigation bar that adapts to the user's authentication status. By leveraging React hooks like useState
and useEffect
, along with Next.js features like Link
and Image
, we’ve built a robust and scalable solution. The use of Tailwind CSS for styling ensures that the component is both responsive and visually appealing.
As developers, we often encounter complex requirements, but with a clear plan and a step-by-step approach, even the most intricate components can be built efficiently. Whether you're a beginner or an experienced developer, understanding how to structure and implement such components is a valuable skill that will serve you well in your projects.
So, the next time you’re faced with a challenging task, remember to break it down, tackle one piece at a time, and don’t hesitate to refer back to examples like this one. Happy coding! 🚀
P.S. If you found this breakdown helpful, feel free to share it with others or leave a comment below. And don’t forget to check out the previous post for the folder structure and more insights into this project!
Top comments (0)