React Context API
Built-in state sharing using React's Context API and useContext hook
React Context API
Context provides a way to pass data through the component tree without having to pass props down manually at every level. It's built into React and is perfect for sharing "global" data like themes, user authentication, or language preferences.
When to Use Context
- Theme — Light/dark mode across the app
- User Auth — Current user data
- Locale — Language and formatting preferences
- Feature Flags — Enable/disable features
- Avoiding Prop Drilling — Data needed by many components
Creating and Using Context
import { createContext, useContext, useState } from 'react';
// 1. Create the context with a default value
const ThemeContext = createContext('light');
// 2. Create a provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// Value object contains everything consumers need
const value = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom hook for easy consumption
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// 4. Use in components
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={theme === 'dark' ? 'bg-gray-900' : 'bg-white'}>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
</header>
);
}
// 5. Wrap your app with the provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<Footer />
</ThemeProvider>
);
}
Authentication Context Example
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check for existing session on mount
const checkAuth = async () => {
try {
const response = await fetch('/api/auth/me');
if (response.ok) {
const userData = await response.json();
setUser(userData);
}
} catch (error) {
console.error('Auth check failed:', error);
} finally {
setLoading(false);
}
};
checkAuth();
}, []);
const login = async (email, password) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
return { success: true };
}
return { success: false, error: 'Invalid credentials' };
};
const logout = async () => {
await fetch('/api/auth/logout', { method: 'POST' });
setUser(null);
};
const value = {
user,
loading,
login,
logout,
isAuthenticated: !!user,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
// Usage in components
function ProfilePage() {
const { user, loading, logout } = useAuth();
if (loading) return <div>Loading...</div>;
if (!user) return <div>Please log in</div>;
return (
<div>
<h1>Welcome, {user.name}!</h1>
<button onClick={logout}>Log Out</button>
</div>
);
}
Multiple Contexts
// Compose multiple providers
function App() {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<NotificationProvider>
<Router>
<AppContent />
</Router>
</NotificationProvider>
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
// Or create a combined provider
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
Optimizing Context Performance
import { createContext, useContext, useState, useMemo, useCallback } from 'react';
// Problem: Object reference changes every render
function BadProvider({ children }) {
const [count, setCount] = useState(0);
// ❌ New object every render = unnecessary re-renders
const value = { count, setCount };
return <Context.Provider value={value}>{children}</Context.Provider>;
}
// Solution: Memoize the value object
function GoodProvider({ children }) {
const [count, setCount] = useState(0);
// ✅ Memoize the value
const value = useMemo(() => ({
count,
increment: () => setCount(c => c + 1),
decrement: () => setCount(c => c - 1),
}), [count]);
return <Context.Provider value={value}>{children}</Context.Provider>;
}
// Better: Split into separate contexts
const CountContext = createContext(0);
const CountActionsContext = createContext(null);
function OptimizedProvider({ children }) {
const [count, setCount] = useState(0);
// Actions never change, so components using only actions won't re-render
const actions = useMemo(() => ({
increment: () => setCount(c => c + 1),
decrement: () => setCount(c => c - 1),
}), []);
return (
<CountContext.Provider value={count}>
<CountActionsContext.Provider value={actions}>
{children}
</CountActionsContext.Provider>
</CountContext.Provider>
);
}
// Now components can subscribe to only what they need
function Display() {
const count = useContext(CountContext); // Re-renders when count changes
return <span>{count}</span>;
}
function Buttons() {
const { increment, decrement } = useContext(CountActionsContext);
// Never re-renders when count changes!
return (
<div>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
Context API Pros & Cons
| Pros | Cons |
|---|---|
| Built into React, no extra dependencies | All consumers re-render on any value change |
| Simple API, easy to understand | No built-in devtools |
| Perfect for low-frequency updates | Can become verbose with multiple contexts |
| Works with SSR out of the box | Performance issues with frequent updates |
💡 Best Practices
- • Always create a custom hook (useTheme, useAuth) for consuming context
- • Throw an error if hook is used outside provider
- • Keep contexts small and focused on one concern
- • Memoize the value object to prevent unnecessary re-renders
- • Split state and actions into separate contexts for optimization
- • Consider external libraries for frequent updates