Context for global state

Passing props down one level is easy. Passing props through five levels of components that do not even use them is prop drilling, and it gets painful fast. Context lets you put a value once at the top of the tree and read it from anywhere inside.

AuthContext.tsx
tsx
import { createContext, useContext, useState } from 'react';

interface AuthState {
  token: string | null;
  login: (token: string) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthState | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [token, setToken] = useState<string | null>(() => localStorage.getItem('token'));

  const login = (newToken: string) => {
    localStorage.setItem('token', newToken);
    setToken(newToken);
  };

  const logout = () => {
    localStorage.removeItem('token');
    setToken(null);
  };

  return (
    <AuthContext.Provider value={{ token, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
  return ctx;
}

Create a context, provide it at the top, consume it anywhere below.

Read the Provider carefully. It owns the state and exposes login and logout functions. Any component inside can call useAuth() to grab the current token or trigger login and logout. No prop drilling, no tangled parent chains, no global variables.

One warning. Context is for state that many components need, not for every piece of state. If you put too much in Context, every change re-renders the whole tree. Keep Contexts small and focused: one for auth, one for theme, one for cart. Not one giant AppContext with everything.

Quiz: Quiz

Loading practice…