typescript 131 lines · 4 tabs

Protect React Routes with an Auth Context and a RequireAuth Wrapper

Shared by codesnips Aug 2026
4 tabs
import { createContext, useContext, useEffect, useMemo, useState, ReactNode } from 'react';
import { authApi, User } from './authApi';

type Status = 'loading' | 'authenticated' | 'anonymous';

interface AuthValue {
  user: User | null;
  status: Status;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
}

const AuthContext = createContext<AuthValue | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [status, setStatus] = useState<Status>('loading');

  useEffect(() => {
    let active = true;
    authApi
      .me()
      .then((u) => active && (setUser(u), setStatus('authenticated')))
      .catch(() => active && setStatus('anonymous'));
    return () => {
      active = false;
    };
  }, []);

  const value = useMemo<AuthValue>(
    () => ({
      user,
      status,
      async signIn(email, password) {
        const u = await authApi.login(email, password);
        setUser(u);
        setStatus('authenticated');
      },
      async signOut() {
        await authApi.logout();
        setUser(null);
        setStatus('anonymous');
      },
    }),
    [user, status],
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth(): AuthValue {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within an AuthProvider');
  return ctx;
}
4 files · typescript Explain with highlit

This snippet shows the standard React pattern for gating routes behind authentication using a context provider plus a wrapper component, rather than sprinkling auth checks inside every page. The idea is to centralize "who is the user and are we still figuring that out" in one place, then let route guards consume that single source of truth.

In AuthContext.tsx, an AuthContext holds the current user, a status flag, and signIn/signOut methods. The AuthProvider starts in a loading status and runs an effect on mount that calls authApi.me() to restore an existing session from an httpOnly cookie. This bootstrap step is important: without it, a page refresh would momentarily treat an authenticated user as logged out and bounce them to the login screen. The value is memoized with useMemo so consumers don't re-render on every provider render. The exported useAuth hook throws when used outside the provider, which turns a silent undefined bug into a loud, immediate error during development.

In RequireAuth.tsx, the guard reads status and user from useAuth. While status === 'loading' it renders a fallback instead of redirecting — this prevents the refresh flicker described above. When there is no user, it renders React Router's <Navigate> to /login, passing the current location in state so the login page can send the user back where they intended to go. replace is used so the protected URL doesn't linger in browser history. When a user exists it renders <Outlet />, letting nested routes proceed.

In AppRoutes.tsx, RequireAuth wraps a parent <Route> whose children inherit the guard, so the protected subtree is declared once. LoginPage.tsx demonstrates the redirect-back flow: after signIn succeeds it reads location.state.from and navigates there, defaulting to /.

The main trade-off is that this is client-side enforcement only — it improves UX but the server must still authorize every request. The pattern's strength is separation: authentication state lives in context, redirect policy lives in the guard, and route structure stays declarative.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Protect React Routes with an Auth Context and a RequireAuth Wrapper — share card
Link copied