Code To Learn logo

Code To Learn

M7: Forms & Authentication

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:

src/App.jsx
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:

src/app/global.css
/* 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!"

Conditional rendering:

{user && <Navbar />}

How it works:

Logical AND evaluation:

// When user is null:
{null && <Navbar />}  → null (nothing rendered)

// When user exists:
{{ id: 1, name: 'John' } && <Navbar />}  → <Navbar /> (rendered!)

JavaScript logic:

// Falsy values return first operand:
null && anything        → null
undefined && anything   → undefined
false && anything       → false
0 && anything          → 0
'' && anything         → ''

// Truthy values return second operand:
{} && anything         → anything
'text' && anything     → anything
true && anything       → anything

In JSX:

// Falsy values (except 0) don't render:
{null}       → (nothing)
{undefined}  → (nothing)
{false}      → (nothing)
{true}       → (nothing)

// But 0 renders as "0":
{0}          → "0" (careful!)

// Objects and components render:
{<Navbar />} → Navbar component

That's why this works:

{user && <Navbar />}

// user is null → nothing rendered
// user is object → Navbar rendered

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:

  1. Loading spinner (briefly)
  2. No Navbar (since not signed in)
  3. 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 yet

Test with React DevTools

Check AuthProvider state:

user: null
token: null
isLoading: false

Navbar 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 prompt

Timeline:

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 again

Timeline (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 users

What's Next?

In Lesson 5, we'll create the SignInPage where users can actually sign in! We'll build:

  1. Page layout
  2. Form container
  3. Error message display
  4. 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