javascript 62 lines · 8 steps

Building an auth context in React

A reducer-backed React context that persists a token, exposes login and logout, and guards its own consumers.

Explained by highlit
1import { createContext, useContext, useReducer, useCallback, useEffect } from 'react';
2 
3const AuthContext = createContext(null);
4 
5const initialState = { user: null, token: null, status: 'idle', error: null };
6 
7function authReducer(state, action) {
8 switch (action.type) {
9 case 'LOGIN_START':
10 return { ...state, status: 'loading', error: null };
11 case 'LOGIN_SUCCESS':
12 return { ...state, status: 'authenticated', user: action.user, token: action.token };
13 case 'LOGIN_ERROR':
14 return { ...state, status: 'error', error: action.error };
15 case 'LOGOUT':
16 return { ...initialState, status: 'idle' };
17 default:
18 return state;
19 }
20}
21 
22export function AuthProvider({ children }) {
23 const [state, dispatch] = useReducer(authReducer, initialState, (init) => {
24 const token = localStorage.getItem('token');
25 return token ? { ...init, token, status: 'authenticated' } : init;
26 });
27 
28 useEffect(() => {
29 if (state.token) localStorage.setItem('token', state.token);
30 else localStorage.removeItem('token');
31 }, [state.token]);
32 
33 const login = useCallback(async (credentials) => {
34 dispatch({ type: 'LOGIN_START' });
35 try {
36 const res = await fetch('/api/auth/login', {
37 method: 'POST',
38 headers: { 'Content-Type': 'application/json' },
39 body: JSON.stringify(credentials),
40 });
41 if (!res.ok) throw new Error('Invalid credentials');
42 const { user, token } = await res.json();
43 dispatch({ type: 'LOGIN_SUCCESS', user, token });
44 } catch (err) {
45 dispatch({ type: 'LOGIN_ERROR', error: err.message });
46 }
47 }, []);
48 
49 const logout = useCallback(() => dispatch({ type: 'LOGOUT' }), []);
50 
51 return (
52 <AuthContext.Provider value={{ ...state, login, logout }}>
53 {children}
54 </AuthContext.Provider>
55 );
56}
57 
58export function useAuth() {
59 const ctx = useContext(AuthContext);
60 if (!ctx) throw new Error('useAuth must be used within an AuthProvider');
61 return ctx;
62}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A reducer with a status field turns auth into an explicit state machine instead of scattered booleans.
  2. 2Lazy reducer initialization plus an effect keeps in-memory state and localStorage in sync in both directions.
  3. 3A custom hook that throws on a missing provider makes misuse fail loudly at development time.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building an auth context in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code