L4: Hide Navbar When Not Signed In
Implement conditional UI based on authentication state
Let's implement conditional rendering based on authentication state so the Navbar only appears when users are signed in! 🎨
Why Hide the Navbar?
Current problem:
User NOT signed in
→ Navbar still shows
→ "Favorites" link visible
→ But favorites require authentication!
→ Confusing UX 😕Better UX:
User NOT signed in
→ Hide Navbar
→ Show sign-in page
→ Clean, focused interface ✨
User signed in
→ Show Navbar
→ User can navigate
→ Access authenticated features 🎉Understanding Conditional Rendering
Update App Component
Add conditional rendering for the Navbar based on auth state:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from '@/components/AuthProvider';
import HomePage from '@/pages/HomePage';
import FavoritesPage from '@/pages/FavoritesPage';
import NotFoundPage from '@/pages/NotFoundPage';
import Navbar from '@/components/Navbar';
import { useAuth } from '@/components/AuthProvider';
function AppContent() {
const { user, isLoading } = useAuth();
// Show loading spinner while checking authentication
if (isLoading) {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
return (
<div className="app">
{/* Only show Navbar if user is signed in */}
{user && <Navbar />}
<main className="main-content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/favorites" element={<FavoritesPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</main>
</div>
);
}
function App() {
return (
<AuthProvider>
<BrowserRouter>
<AppContent />
</BrowserRouter>
</AuthProvider>
);
}
export default App;Add Loading Styles
Add CSS for the loading spinner:
/* Loading Container */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background: #f9fafb;
}
/* Spinner Animation */
.spinner {
width: 48px;
height: 48px;
border: 4px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-container p {
margin-top: 1rem;
color: #6b7280;
font-size: 0.875rem;
}Understanding the Code
Why two components?
// AppContent uses useAuth()
function AppContent() {
const { user, isLoading } = useAuth(); // ✅ Works!
// ...
}
// App wraps with AuthProvider
function App() {
return (
<AuthProvider>
<BrowserRouter>
<AppContent /> {/* Can use useAuth */}
</BrowserRouter>
</AuthProvider>
);
}Why not this?
// ❌ Won't work!
function App() {
const { user } = useAuth(); // ERROR: Outside AuthProvider!
return (
<AuthProvider>
{/* ... */}
</AuthProvider>
);
}Rule: You can only use useAuth() inside components wrapped by <AuthProvider>.
Solution: Create AppContent inside the provider:
App (no auth access)
└── AuthProvider
└── BrowserRouter
└── AppContent (has auth access ✅)Loading state check:
const { user, isLoading } = useAuth();
if (isLoading) {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}Why this matters:
Without loading check:
User opens app
→ isLoading: true, user: null
→ Shows sign-in page (wrong!)
→ 100ms later: user data arrives
→ Switches to home page
→ Jarring transition 😕With loading check:
User opens app
→ isLoading: true
→ Shows loading spinner
→ 100ms later: user data arrives
→ isLoading: false
→ Shows home page with Navbar
→ Smooth experience ✨User perception:
Without loading: "Why did it flash the sign-in page?"
With loading: "App is checking my session, nice!"Layout without Navbar:
return (
<div className="app">
{/* No Navbar here */}
<main className="main-content">
<Routes>...</Routes>
</main>
</div>
);Layout with Navbar:
return (
<div className="app">
<Navbar /> {/* Takes up space */}
<main className="main-content">
<Routes>...</Routes>
</main>
</div>
);CSS handles both:
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-content {
flex: 1; /* Takes remaining space */
/* Whether Navbar exists or not */
}Result:
With Navbar:
┌─────────────────┐
│ Navbar (60px) │
├─────────────────┤
│ │
│ Main Content │ ← Fills rest
│ │
└─────────────────┘
Without Navbar:
┌─────────────────┐
│ │
│ │
│ Main Content │ ← Fills all
│ │
│ │
└─────────────────┘Testing the Conditional Rendering
Refresh the page
You should see:
- Loading spinner (briefly)
- No Navbar (since not signed in)
- Routes still work (HomePage loads)
Check browser console
// In AuthProvider useEffect:
No valid session: Request failed with status code 401
// This is expected - no user signed in yetTest with React DevTools
Check AuthProvider state:
user: null
token: null
isLoading: falseNavbar should NOT be in the component tree.
Navigate between pages
Try visiting:
/- HomePage (works)/favorites- FavoritesPage (works)/random- NotFoundPage (works)
All routes work, but no Navbar visible.
User Experience Flow
Timeline:
0ms: User opens app
→ AuthProvider mounts
→ isLoading: true
→ Show loading spinner
100ms: Check for token
→ API call to /auth/refresh
→ 401 Unauthorized (no token)
→ user: null, token: null
→ isLoading: false
101ms: Render app
→ No Navbar (user is null)
→ Show routes normally
→ HomePage visible
User sees:
1. Brief loading spinner
2. Homepage without Navbar
3. Eventually will see sign-in promptTimeline:
0ms: User returns (session expired)
→ AuthProvider mounts
→ isLoading: true
→ Show loading spinner
100ms: Check for token
→ API call to /auth/refresh
→ 401 Unauthorized (expired token)
→ user: null, token: null
→ isLoading: false
101ms: Render app
→ No Navbar
→ Show routes
→ HomePage visible
User sees:
→ Same as first visit
→ Must sign in againTimeline (we'll implement token fetch in next lessons):
0ms: User returns (session valid)
→ AuthProvider mounts
→ isLoading: true
→ Show loading spinner
100ms: Check for token
→ API call to /auth/refresh
→ 200 OK (valid token!)
→ user: { id, name, email }
→ token: "eyJhbG..."
→ isLoading: false
101ms: Render app
→ Navbar visible ✅
→ User data available
→ Full app access
User sees:
1. Brief loading spinner
2. App with Navbar
3. Still signed in! 🎉Common Loading Patterns
What We've Achieved
Before this lesson:
✅ AuthProvider created
✅ Added to App
✅ Token fetch on mount
❌ Navbar always visible (confusing)After this lesson:
✅ AuthProvider created
✅ Added to App
✅ Token fetch on mount
✅ Navbar conditionally rendered
✅ Loading state handled
✅ Clean UX for signed-out usersWhat's Next?
In Lesson 5, we'll create the SignInPage where users can actually sign in! We'll build:
- Page layout
- Form container
- Error message display
- Navigation after success
✅ Lesson Complete! The Navbar now only appears when users are signed in!
Key Takeaways
- ✅ Conditional rendering shows/hides UI based on state
- ✅ Loading states prevent jarring transitions
- ✅ Split components allow using context hooks
- ✅ Logical AND (&&) perfect for show/hide pattern
- ✅ User experience improved with smooth loading feedback
- ✅ Early returns keep code clean and readable
- ✅ isLoading check essential for good UX