Adding Authentication to React AppsEntry 6 of 11

Updating the UI Based on Auth State

A loader on the root route returns the token, and any component reads it with useRouteLoaderData('root') to show or hide UI.

Notes

  • Give the root route id: 'root' and loader: tokenLoader.
  • Navigation and pages use useRouteLoaderData('root') to show Login vs Logout, or hide Edit/Delete buttons for logged-out users.
  • React Router revalidates loaders after every action, so the root loader re-runs after login and logout and the UI updates automatically.
  • Hiding buttons is UX only. Protect the routes (Entry 8) and enforce on the backend.

What / Why / When

What

App-wide access to auth state.

Why

UI reflects whether the user is logged in, without Context or Redux.

When

Conditional navigation links, action buttons, user-specific UI.

Code compare

React
// App.tsx{  path: '/',  element: <RootLayout />,  errorElement: <ErrorPage />,  id: 'root',  loader: tokenLoader,  children: [/* ... */],}
React
// components/MainNavigation.tsximport { Form, NavLink, useRouteLoaderData } from "react-router";import type { AuthToken } from "../util/auth";​export default function MainNavigation() {  const token = useRouteLoaderData("root") as AuthToken;  const isLoggedIn = token && token !== "EXPIRED";​  return (    <header>      <nav>        <ul>          <li>            <NavLink to="/" end>              Home            </NavLink>          </li>          <li>            <NavLink to="/events">Events</NavLink>          </li>          {!isLoggedIn && (            <li>              <NavLink to="/auth?mode=login">Authentication</NavLink>            </li>          )}          {isLoggedIn && (            <li>              <Form action="/logout" method="post">                <button>Logout</button>              </Form>            </li>          )}        </ul>      </nav>    </header>  );}
React
// components/EventItem.tsxconst token = useRouteLoaderData("root") as AuthToken;​{  token && token !== "EXPIRED" && (    <menu>      <Link to="edit">Edit</Link>      <button onClick={startDeleteHandler}>Delete</button>    </menu>  );}
Angular
@Component({  selector: "app-main-navigation",  imports: [RouterLink, RouterLinkActive],  template: `    <header>      <nav>        <ul>          <li>            <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }"              >Home</a            >          </li>          <li><a routerLink="/events" routerLinkActive="active">Events</a></li>          @if (!isAuthenticated()) {            <li><a routerLink="/auth" [queryParams]="{ mode: 'login' }">Authentication</a></li>          } @else {            <li><button (click)="logout()">Logout</button></li>          }        </ul>      </nav>    </header>  `,})export class MainNavigationComponent {  private authService = inject(AuthService);  protected isAuthenticated = this.authService.isAuthenticated;​  logout() {    this.authService.logout();  }}

Differences and gotchas

  • React's auth UI updates through loader revalidation; changing localStorage outside an action or navigation won't update it.
  • Angular's auth UI updates through the signal, immediately and anywhere.

Quick reference

TaskReactAngular
Expose auth stateRoot loader: tokenLoader, id: 'root'AuthService.isAuthenticated signal
Read ituseRouteLoaderData('root')inject(AuthService).isAuthenticated()
UI refresh triggerLoader revalidation after actionsSignal change