React Context API
Sharing data across component tree without props
🌐 What is Context API?
Context API provides a way to share values between components without passing props through every level. It's perfect for global data like themes, user authentication, or language preferences.
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
Context API Concepts
Create Context
Initialize a new context object
const MyContext = createContext(defaultValue);
Provider
Supplies context value to children
<MyContext.Provider value={data}>
Consumer
Accesses context value in components
const value = useContext(MyContext);
No Prop Drilling
Skip intermediate components
// Direct access anywhere in tree
🔹 Basic Context Example
Create a simple theme context to share theme data across components. This eliminates the need to pass theme props through every component level.
import { createContext, useContext, useState } from 'react';
// 1. Create Context
const ThemeContext = createContext();
// 2. Create Provider Component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Create Custom Hook for Easy Access
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 4. Use in Components
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff'
}}
>
Current theme: {theme}
</button>
);
}
// 5. Wrap App with Provider
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}
Output:
🔹 User Authentication Context
A practical example managing user authentication state. This context provides login, logout functionality and user data throughout your application without prop drilling.
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (username, password) => {
// Simulate API call
setUser({ username, id: 1 });
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
return useContext(AuthContext);
}
// Using in components
function LoginButton() {
const { user, login, logout } = useAuth();
if (user) {
return (
<div>
<p>Welcome, {user.username}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
return (
<button onClick={() => login('john', 'password123')}>
Login
</button>
);
}
function App() {
return (
<AuthProvider>
<LoginButton />
</AuthProvider>
);
}
Output (Before Login):
Output (After Login):
Welcome, john!
🔹 Multiple Contexts
You can use multiple contexts in your application. Nest providers to combine different global states like theme, authentication, and language settings.
import { createContext, useContext } from 'react';
const ThemeContext = createContext();
const LanguageContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<LanguageContext.Provider value="en">
<Dashboard />
</LanguageContext.Provider>
</ThemeContext.Provider>
);
}
function Dashboard() {
const theme = useContext(ThemeContext);
const language = useContext(LanguageContext);
return (
<div>
<p>Theme: {theme}</p>
<p>Language: {language}</p>
</div>
);
}
🔹 Context with useReducer
Combine Context API with useReducer for complex state management. This pattern is great for managing application-wide state with multiple actions.
import { createContext, useContext, useReducer } from 'react';
const CartContext = createContext();
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.item];
case 'REMOVE_ITEM':
return state.filter(item => item.id !== action.id);
case 'CLEAR_CART':
return [];
default:
return state;
}
}
function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, []);
const addItem = (item) => dispatch({ type: 'ADD_ITEM', item });
const removeItem = (id) => dispatch({ type: 'REMOVE_ITEM', id });
const clearCart = () => dispatch({ type: 'CLEAR_CART' });
return (
<CartContext.Provider value={{ cart, addItem, removeItem, clearCart }}>
{children}
</CartContext.Provider>
);
}
function useCart() {
return useContext(CartContext);
}
function ShoppingCart() {
const { cart, addItem, removeItem } = useCart();
return (
<div>
<h2>Cart ({cart.length} items)</h2>
<button onClick={() => addItem({ id: 1, name: 'Product' })}>
Add Item
</button>
{cart.map(item => (
<div key={item.id}>
{item.name}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
</div>
);
}
🔹 When to Use Context API
Context is powerful but should be used appropriately:
✅ Use Context when:
- Global data: Theme, authentication, language settings
- Prop drilling: Passing props through many levels
- Shared state: Multiple components need same data
- Configuration: App-wide settings and preferences
❌ Don't use Context when:
- Frequent updates: Can cause performance issues
- Local state: Data only needed in one component
- Simple prop passing: Only one or two levels deep
- Complex state logic: Consider state management libraries
🔹 Context Best Practices
Follow these patterns for effective Context usage:
// ✅ Good: Separate context file
// contexts/ThemeContext.js
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// ✅ Good: Split contexts by concern
// Don't put everything in one context
// ✅ Good: Memoize context value for performance
const value = useMemo(() => ({ theme, setTheme }), [theme]);
// ❌ Bad: Creating new object every render
<Context.Provider value={{ theme, setTheme }}>