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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A reducer with a status field turns auth into an explicit state machine instead of scattered booleans.
- 2Lazy reducer initialization plus an effect keeps in-memory state and localStorage in sync in both directions.
- 3A custom hook that throws on a missing provider makes misuse fail loudly at development time.
Related explainers
javascript
const legacyRedirects = [ { from: '/blog/:slug', to: '/articles/:slug' }, { from: '/shop/product/:id', to: '/store/items/:id' }, { from: '/about-us', to: '/about' },
How redirects work in Next.js config
redirects
routing
configuration
Intermediate
7 steps
go
package middleware import ( "crypto/hmac"
Verifying signed URLs with Gin middleware
hmac
middleware
authentication
Intermediate
8 steps
rust
use axum::{ extract::{FromRequestParts, Path, Query}, http::{request::Parts, StatusCode}, response::{IntoResponse, Redirect},
Signed download links as an Axum extractor
hmac
custom-extractor
authentication
Advanced
9 steps
javascript
import { useDeferredValue, useMemo, useState } from "react"; function ProductSearch({ products }) { const [query, setQuery] = useState("");
Keeping search input snappy with useDeferredValue in React
concurrent-rendering
deferred-value
memoization
Intermediate
7 steps
rust
use axum::{ extract::{FromRef, FromRequestParts}, http::{header, request::Parts, StatusCode}, RequestPartsExt,
How a JWT extractor works in Axum
jwt
authentication
extractors
Intermediate
8 steps
javascript
const IBAN_LENGTHS = { DE: 22, FR: 27, GB: 22, ES: 24, IT: 27, NL: 18, BE: 16, CH: 21, AT: 20, PT: 25, };
How IBAN validation works in JavaScript
validation
checksum
modular-arithmetic
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-an-auth-context-in-react-explained-javascript-ce70/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.