Reading the Navigation
Now let's explore a data-driven component - the Navigation. This component demonstrates crucial patterns: arrays, mapping, and conditional rendering.
What You'll Learn
- How to work with arrays of data
- The
.map()method for rendering lists - Client Components vs Server Components
- Responsive design (mobile vs desktop)
- Component libraries (Headless UI)
- Conditional rendering patterns
Open the File
In VS Code, open:
components/layout/Navigation.tsx
This component is more complex than Header - it has logic!
The Complete File
"use client";
import Link from "next/link";
import { Disclosure } from '@headlessui/react';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
const navItems = [
{ name: "About", href: "/about" },
{ name: "The Model", href: "/model" },
{ name: "Future Programs", href: "/programs" },
{ name: "Updates", href: "/updates" },
{ name: "Launching", href: "/launching" },
{ name: "Learning Guide", href: "/learn" },
{ name: "Get Involved", href: "/get-involved" },
{ name: "Contact", href: "/contact" },
];
export default function Navigation() {
return (
<Disclosure as="nav" className="flex items-center">
{/* Desktop Navigation */}
<div className="hidden lg:flex items-center space-x-1">
{navItems.map((item) => (
<Link
key={item.name}
href={item.href}
className="relative text-gray-700 hover:text-blue-600 transition-all duration-200 font-medium px-4 py-2 rounded-lg hover:bg-blue-50 group"
>
{item.name}
<span className="absolute bottom-0 left-1/2 w-0 h-0.5 bg-blue-600 transition-all duration-200 group-hover:w-3/4 transform -translate-x-1/2"></span>
</Link>
))}
</div>
{/* Mobile Navigation */}
<div className="lg:hidden">
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-lg text-gray-700 hover:text-blue-600 hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all duration-200 shadow-sm">
<span className="sr-only">Open main menu</span>
<Bars3Icon className="block h-6 w-6 group-data-[open]:hidden" aria-hidden="true" />
<XMarkIcon className="hidden h-6 w-6 group-data-[open]:block" aria-hidden="true" />
</Disclosure.Button>
<Disclosure.Panel className="absolute top-20 left-0 right-0 bg-white/95 backdrop-blur-md shadow-xl border-t border-gray-100 z-50">
<div className="px-6 py-4 space-y-2">
{navItems.map((item) => (
<Disclosure.Button
key={item.name}
as={Link}
href={item.href}
className="block px-4 py-3 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium border border-transparent hover:border-blue-100"
>
{item.name}
</Disclosure.Button>
))}
</div>
</Disclosure.Panel>
</div>
</Disclosure>
);
}
Looks complex! Let's break it down step by step.
Part 1: The "use client" Directive (Line 1)
"use client";
What This Means
Client Component:
- Runs in the browser (not on the server)
- Can use React hooks and browser APIs
- Can handle interactivity (clicks, state, etc.)
Why needed here:
- Navigation has interactive elements (mobile menu toggle)
- Uses Headless UI components (require client-side JavaScript)
- Responds to user clicks
Server Components (default):
- Render on the server
- Can't use hooks or browser APIs
- Better for static content
Rule of thumb: Add "use client" when you need interactivity!
Part 2: Imports (Lines 3-5)
import Link from "next/link";
import { Disclosure } from '@headlessui/react';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
Line-by-Line Breakdown
Line 3: import Link from "next/link"
- We've seen this before
- For navigation between pages
Line 4: import { Disclosure } from '@headlessui/react'
- External library: Headless UI
Disclosure: Toggleable component (show/hide menu)- Curly braces
{ }: Named import (not default)
Line 5: import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
- External library: Heroicons
Bars3Icon: Hamburger menu icon (≡)XMarkIcon: Close icon (×)- Icons as React components!
Why these libraries?
- Headless UI: Accessible, keyboard-friendly components
- Heroicons: High-quality SVG icons from Tailwind team
Part 3: Navigation Data (Lines 7-16)
const navItems = [
{ name: "About", href: "/about" },
{ name: "The Model", href: "/model" },
{ name: "Future Programs", href: "/programs" },
{ name: "Updates", href: "/updates" },
{ name: "Launching", href: "/launching" },
{ name: "Learning Guide", href: "/learn" },
{ name: "Get Involved", href: "/get-involved" },
{ name: "Contact", href: "/contact" },
];
Understanding the Data Structure
const navItems:
- Constant variable (won't change)
- Lives outside the component function
- Available to entire component
= [ ... ]:
- JavaScript array
- Contains multiple objects
- Each object is one navigation item
Each object has:
name: What displays to usershref: Where the link goes
Why Use an Array?
Benefits:
- Easy to add/remove links: Just edit the array
- No duplicate code: Write render logic once
- Easy to maintain: All links in one place
Alternative (bad approach):
// Don't do this! Too repetitive
<Link href="/about">About</Link>
<Link href="/model">The Model</Link>
<Link href="/programs">Future Programs</Link>
// ... repeat 8 times!
Pattern to remember: When you have repeated elements with different data, use an array + map!
Part 4: Component Function (Line 18)
export default function Navigation() {
return (
<Disclosure as="nav" className="flex items-center">
Disclosure Component
<Disclosure>:
- From Headless UI library
- Manages show/hide state automatically
- Handles accessibility (keyboard navigation, screen readers)
as="nav":
- Renders as a
<nav>HTML element - Semantic HTML for navigation sections
Why Disclosure?
- Easier than managing state yourself
- Automatic accessibility features
- Toggle open/close with built-in logic
Part 5: Desktop Navigation (Lines 21-32)
{/* Desktop Navigation */}
<div className="hidden lg:flex items-center space-x-1">
{navItems.map((item) => (
<Link
key={item.name}
href={item.href}
className="relative text-gray-700 hover:text-blue-600 transition-all duration-200 font-medium px-4 py-2 rounded-lg hover:bg-blue-50 group"
>
{item.name}
<span className="absolute bottom-0 left-1/2 w-0 h-0.5 bg-blue-600 transition-all duration-200 group-hover:w-3/4 transform -translate-x-1/2"></span>
</Link>
))}
</div>
Responsive Visibility (Line 22)
className="hidden lg:flex ...":
hidden: Hidden by default (mobile)lg:flex: Show as flex on large screens- Result: Desktop-only navigation
Breakpoint: lg: means ≥ 1024px width
The Map Method (Line 23)
{navItems.map((item) => (
<Link key={item.name} href={item.href}>
{item.name}
</Link>
))}
How .map() works:
- Takes each object from
navItemsarray - Calls the function for each item
- Function returns JSX for that item
- All JSX gets rendered
Example with 3 items:
// navItems = [
// { name: "About", href: "/about" },
// { name: "Model", href: "/model" },
// { name: "Programs", href: "/programs" }
// ]
// Becomes:
<Link href="/about">About</Link>
<Link href="/model">Model</Link>
<Link href="/programs">Programs</Link>
This is the most important pattern in React!
The Key Prop (Line 24)
key={item.name}
Why needed:
- React needs to track which items changed
keymust be unique for each item- Helps React efficiently update the DOM
Common keys:
key={item.id}(if you have unique IDs)key={item.name}(if names are unique)key={index}(last resort, not ideal)
Warning: React will show an error if you forget key in mapped elements!
Link Styling (Lines 27-28)
className="relative text-gray-700 hover:text-blue-600 transition-all duration-200 font-medium px-4 py-2 rounded-lg hover:bg-blue-50 group"
Styling breakdown:
relative: Needed for the underline animationtext-gray-700: Default gray texthover:text-blue-600: Blue on hovertransition-all duration-200: Smooth animationpx-4 py-2: Internal padding (makes it clickable)rounded-lg: Rounded cornershover:bg-blue-50: Light blue background on hovergroup: Enables child hover effects
Animated Underline (Line 30)
<span className="absolute bottom-0 left-1/2 w-0 h-0.5 bg-blue-600 transition-all duration-200 group-hover:w-3/4 transform -translate-x-1/2"></span>
This creates a cool underline animation!
How it works:
absolute: Positioned relative to the linkbottom-0: At the bottomleft-1/2: Centered horizontallyw-0: Width is 0 (invisible by default)group-hover:w-3/4: Grows to 75% width on hoverh-0.5: Thin line (2px)bg-blue-600: Blue colortransform -translate-x-1/2: Centers it (adjusts for left-1/2)
Result: Hover over a link, underline animates from center outward!
Pattern: Absolute-positioned spans for decorative effects
Part 6: Mobile Navigation (Lines 34-55)
{/* Mobile Navigation */}
<div className="lg:hidden">
className="lg:hidden":
- Visible by default (mobile)
- Hidden on large screens (lg:hidden)
- Opposite of desktop navigation!
Mobile Menu Button (Lines 35-39)
<Disclosure.Button className="...">
<span className="sr-only">Open main menu</span>
<Bars3Icon className="block h-6 w-6 group-data-[open]:hidden" />
<XMarkIcon className="hidden h-6 w-6 group-data-[open]:block" />
</Disclosure.Button>
<Disclosure.Button>:
- Special component from Headless UI
- Automatically toggles the menu
- No onClick handler needed!
<span className="sr-only">:
- "screen reader only"
- Invisible but read by screen readers
- Important for accessibility!
Icon switching:
<Bars3Icon>: Hamburger menu (≡)blockby defaultgroup-data-[open]:hiddenwhen menu is open
<XMarkIcon>: Close icon (×)hiddenby defaultgroup-data-[open]:blockwhen menu is open
Result: Icon changes from ≡ to × when menu opens!
Pattern: Conditional rendering with Headless UI's data attributes
Mobile Menu Panel (Lines 41-53)
<Disclosure.Panel className="absolute top-20 left-0 right-0 bg-white/95 backdrop-blur-md shadow-xl border-t border-gray-100 z-50">
<div className="px-6 py-4 space-y-2">
{navItems.map((item) => (
<Disclosure.Button
key={item.name}
as={Link}
href={item.href}
className="block px-4 py-3 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium border border-transparent hover:border-blue-100"
>
{item.name}
</Disclosure.Button>
))}
</div>
</Disclosure.Panel>
<Disclosure.Panel>:
- Appears when menu button is clicked
- Automatically hidden by default
- Headless UI manages the show/hide!
Panel positioning:
absolute: Positioned over the pagetop-20: Below the headerleft-0 right-0: Full widthz-50: Appears above other content
Styling:
bg-white/95: White at 95% opacitybackdrop-blur-md: Blur effect behind panelshadow-xl: Large drop shadow
Same map pattern:
- Map over
navItemsagain - Different styling for mobile (vertical stack)
space-y-2: Vertical spacing between links
as={Link}:
- Renders Disclosure.Button as a Link
- Combines button behavior with link navigation
- Clever component composition!
Key Patterns You Learned
Pattern 1: Data-Driven Rendering
const items = [{ name: "A" }, { name: "B" }]
{items.map((item) => (
<div key={item.name}>{item.name}</div>
))}
Most important React pattern!
Pattern 2: Responsive Visibility
<div className="hidden lg:block">Desktop only</div>
<div className="block lg:hidden">Mobile only</div>
Control what shows at different screen sizes
Pattern 3: Client Component Directive
"use client";
Add at top when you need interactivity
Pattern 4: Named Imports
import { Component } from 'library'
Curly braces for named exports
Pattern 5: Conditional Icon Rendering
<Icon1 className="block group-data-[open]:hidden" />
<Icon2 className="hidden group-data-[open]:block" />
Show/hide based on component state
Hands-On Exercises
Exercise 1: Add a New Navigation Link
Add a "Blog" link to the navigation.
Steps:
- Find the
navItemsarray (lines 7-16) - Add a new object at the end:
{ name: "Blog", href: "/blog" },
- Save and check both desktop and mobile navigation
What you'll see: "Blog" appears in both menus automatically!
Why it works: Both menus map over the same array
Exercise 2: Change Link Hover Color
Change links from blue to green on hover.
Find (in desktop nav):
hover:text-blue-600
Change to:
hover:text-green-600
Also change (animated underline):
bg-blue-600 → bg-green-600
And (hover background):
hover:bg-blue-50 → hover:bg-green-50
Do the same in the mobile navigation styles
Exercise 3: Reorder Navigation Items
Move "Contact" to be the first link.
Steps:
- Find the Contact object in navItems array
- Cut it (Cmd+X / Ctrl+X)
- Paste it as the first item in the array
- Save
What you'll see: Contact now appears first in both menus!
Exercise 4: Change Mobile Menu Position
Move the mobile menu to slide from the right instead of dropping from top.
Find (Disclosure.Panel):
className="absolute top-20 left-0 right-0 ..."
Change to:
className="absolute top-0 right-0 bottom-0 w-64 ..."
Experiment with different positioning values!
Exercise 5: Remove the Underline Animation
Remove the animated underline from desktop links.
Find and delete (lines 30):
<span className="absolute bottom-0 left-1/2 w-0 h-0.5 bg-blue-600 transition-all duration-200 group-hover:w-3/4 transform -translate-x-1/2"></span>
Save: Links now have no underline animation
Common Questions
Q: Why use .map() instead of a for loop?
A: JSX doesn't support for loops directly. .map() is the React way to transform arrays into JSX elements.
Q: Can I use different data for desktop vs mobile?
A: Yes! You could have desktopNavItems and mobileNavItems arrays. But using the same data keeps them in sync.
Q: What if I forget the key prop?
A: React will show a warning in the console. The page might still work, but updates will be less efficient.
Q: Why separate desktop and mobile navigation?
A: Different UX patterns. Desktop has space for horizontal links. Mobile needs a collapsible menu to save space.
Q: Can I use map() for other things besides navigation?
A: Absolutely! Use it for:
- Lists of blog posts
- Product cards
- Table rows
- Image galleries
- Any repeated elements!
Troubleshooting
Problem: Added a nav item but it doesn't show
Check:
- Did you save the file?
- Is the object properly formatted with
nameandhref? - Is there a comma after the previous item?
- Check browser console for errors
Problem: Map error: "key is not defined"
Solution:
- Make sure every mapped element has a
keyprop - Key should be unique for each item
Problem: Mobile menu doesn't toggle
Check:
- Is
"use client"at the top of the file? - Are Disclosure components imported?
- Check browser console for errors
Problem: Icons don't show
Solution:
- Make sure Heroicons are installed:
npm install @heroicons/react - Check imports are correct
- Make sure icons are properly capitalized
Quick Reference
Map over array:
{items.map((item) => (
<div key={item.id}>{item.name}</div>
))}
Client component:
"use client";
Responsive visibility:
<div className="hidden lg:block">
Named import:
import { Component } from 'library'
Headless UI Disclosure:
<Disclosure>
<Disclosure.Button>Toggle</Disclosure.Button>
<Disclosure.Panel>Content</Disclosure.Panel>
</Disclosure>
Key prop (required in map):
key={item.id}
What's Next
Now that you've seen data-driven components, let's dive deeper into how data flows between components with props: