useContext

Understanding useContext: React's "Pass Data Without Props" Hook
Hey! Today we'll learn about useContext - it's like creating a global announcement system for your React app where components can listen to important information without passing it through every level.
What Problem Does useContext Solve?
Imagine you're passing a piece of data from a grandparent component to a great-grandchild component:
// ❌ The Prop Drilling Problem
function Grandparent() {
const theme = 'dark';
return <Parent theme={theme} />;
}
function Parent({ theme }) {
return <Child theme={theme} />;
}
function Child({ theme }) {
return <Grandchild theme={theme} />;
}
function Grandchild({ theme }) {
return <p>Theme is: {theme}</p>;
}
That's prop drilling - passing props through components that don't need them just to reach a component that does.
useContext fixes this!
The Basic Idea
Think of useContext like a radio broadcast:
Context.Provider = Radio station (broadcasts data)
useContext() = Radio receiver (listens to data)
Any component can tune in to the broadcast, no matter where it is!
Three Simple Steps to Use Context
Step 1: Create the Context (The Radio Station)
import React, { createContext } from 'react';
// Create the radio station
const ThemeContext = createContext('light'); // Default value = 'light'
// This is like setting up a radio frequency
Step 2: Provide the Value (Start Broadcasting)
function App() {
const [theme, setTheme] = useState('dark');
// Wrap components that need this data in Provider
return (
<ThemeContext.Provider value={theme}>
<Header />
<MainContent />
<Footer />
</ThemeContext.Provider>
);
}
// Now everything inside can listen to the theme!
Step 3: Consume the Value (Tune In)
import React, { useContext } from 'react';
function Header() {
// Tune into the ThemeContext broadcast
const theme = useContext(ThemeContext);
return (
<header className={`header-${theme}`}>
<h1>My App (Theme: {theme})</h1>
</header>
);
}
// No props needed! Header just "knows" the theme
Complete Real Example: Theme Switcher
Let's build a complete theme switcher app:
import React, { createContext, useState, useContext } from 'react';
// 1. CREATE Context (outside components)
const ThemeContext = createContext();
const ThemeUpdateContext = createContext(); // Another context for update function
// Custom hook for easy theme access
function useTheme() {
return useContext(ThemeContext);
}
// Custom hook for easy theme updates
function useThemeUpdate() {
return useContext(ThemeUpdateContext);
}
// 2. PROVIDER Component (wraps our app)
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={theme}>
<ThemeUpdateContext.Provider value={toggleTheme}>
{children}
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
);
}
// 3. COMPONENTS that use the context
function Header() {
const theme = useTheme();
return (
<header style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
padding: '20px'
}}>
<h1>My Website</h1>
<p>Current theme: {theme}</p>
</header>
);
}
function Content() {
const theme = useTheme();
return (
<main style={{
background: theme === 'light' ? '#f5f5f5' : '#222',
color: theme === 'light' ? '#000' : '#fff',
padding: '20px',
minHeight: '200px'
}}>
<p>This is the main content area.</p>
<p>It automatically uses the {theme} theme!</p>
</main>
);
}
function ThemeToggleButton() {
const toggleTheme = useThemeUpdate();
const theme = useTheme();
return (
<button
onClick={toggleTheme}
style={{
padding: '10px 20px',
margin: '10px',
background: theme === 'light' ? '#333' : '#fff',
color: theme === 'light' ? '#fff' : '#333'
}}
>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
}
// 4. PUT IT ALL TOGETHER
function App() {
return (
<ThemeProvider>
<div className="App">
<Header />
<Content />
<ThemeToggleButton />
<Footer />
</div>
</ThemeProvider>
);
}
function Footer() {
const theme = useTheme();
return (
<footer style={{
background: theme === 'light' ? '#eee' : '#111',
color: theme === 'light' ? '#000' : '#fff',
padding: '10px',
marginTop: '20px'
}}>
<p>Footer - Using {theme} theme</p>
</footer>
);
}
Multiple Contexts Example: User + Cart
import React, { createContext, useState, useContext } from 'react';
// Context 1: User Information
const UserContext = createContext();
// Context 2: Shopping Cart
const CartContext = createContext();
function App() {
const [user, setUser] = useState({
name: 'John Doe',
email: 'john@example.com',
isPremium: true
});
const [cart, setCart] = useState([
{ id: 1, name: 'iPhone', price: 999 },
{ id: 2, name: 'Headphones', price: 199 }
]);
const addToCart = (product) => {
setCart([...cart, product]);
};
return (
<UserContext.Provider value={user}>
<CartContext.Provider value={{ cart, addToCart }}>
<Navbar />
<Products />
<Checkout />
</CartContext.Provider>
</UserContext.Provider>
);
}
function Navbar() {
const user = useContext(UserContext);
return (
<nav>
<p>Welcome, {user.name}!</p>
<p>{user.isPremium ? '⭐ Premium User' : 'Free User'}</p>
</nav>
);
}
function Products() {
const { addToCart } = useContext(CartContext);
const products = [
{ id: 3, name: 'Laptop', price: 1299 },
{ id: 4, name: 'Tablet', price: 599 }
];
return (
<div>
<h2>Products</h2>
{products.map(product => (
<div key={product.id} style={{border: '1px solid #ccc', padding: '10px', margin: '10px'}}>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addToCart(product)}>
Add to Cart
</button>
</div>
))}
</div>
);
}
function Checkout() {
const user = useContext(UserContext);
const { cart } = useContext(CartContext);
const total = cart.reduce((sum, item) => sum + item.price, 0);
return (
<div>
<h2>Checkout</h2>
<p>Customer: {user.email}</p>
<h3>Cart Items:</h3>
{cart.map(item => (
<p key={item.id}>{item.name} - ${item.price}</p>
))}
<h3>Total: ${total}</h3>
{user.isPremium && <p>Premium Discount Applied! Final: ${total * 0.9}</p>}
</div>
);
}
Dynamic Context with Updates Example
import React, { createContext, useState, useContext } from 'react';
const SettingsContext = createContext();
function SettingsProvider({ children }) {
const [settings, setSettings] = useState({
language: 'English',
fontSize: 16,
notifications: true,
darkMode: false
});
const updateSetting = (key, value) => {
setSettings(prev => ({
...prev,
[key]: value
}));
};
return (
<SettingsContext.Provider value={{ settings, updateSetting }}>
{children}
</SettingsContext.Provider>
);
}
function SettingsPanel() {
const { settings, updateSetting } = useContext(SettingsContext);
return (
<div style={{border: '2px solid #666', padding: '20px', margin: '20px'}}>
<h2>Settings Panel</h2>
<div>
<label>Language: </label>
<select
value={settings.language}
onChange={(e) => updateSetting('language', e.target.value)}
>
<option>English</option>
<option>Spanish</option>
<option>French</option>
</select>
</div>
<div>
<label>Font Size: {settings.fontSize}px </label>
<input
type="range"
min="12"
max="24"
value={settings.fontSize}
onChange={(e) => updateSetting('fontSize', parseInt(e.target.value))}
/>
</div>
<div>
<label>
<input
type="checkbox"
checked={settings.notifications}
onChange={(e) => updateSetting('notifications', e.target.checked)}
/>
Enable Notifications
</label>
</div>
<div>
<label>
<input
type="checkbox"
checked={settings.darkMode}
onChange={(e) => updateSetting('darkMode', e.target.checked)}
/>
Dark Mode
</label>
</div>
</div>
);
}
function DisplayContent() {
const { settings } = useContext(SettingsContext);
return (
<div style={{
background: settings.darkMode ? '#333' : '#fff',
color: settings.darkMode ? '#fff' : '#000',
fontSize: `${settings.fontSize}px`,
padding: '20px',
margin: '20px'
}}>
<h1>Content Display</h1>
<p>Current language: {settings.language}</p>
<p>Font size: {settings.fontSize}px</p>
<p>Notifications: {settings.notifications ? 'ON' : 'OFF'}</p>
<p>This text changes based on your settings!</p>
</div>
);
}
function App() {
return (
<SettingsProvider>
<div>
<h1>App Settings Demo</h1>
<SettingsPanel />
<DisplayContent />
</div>
</SettingsProvider>
);
}
Custom Hook Pattern (Best Practice)
// Create this in a separate file like 'AuthContext.js'
import React, { createContext, useState, useContext } from 'react';
const AuthContext = createContext();
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const login = async (email, password) => {
setLoading(true);
// Simulate API call
setTimeout(() => {
setUser({ email, name: 'John Doe', token: 'abc123' });
setLoading(false);
}, 1000);
};
const logout = () => {
setUser(null);
};
const value = {
user,
login,
logout,
loading,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// In your components:
function LoginButton() {
const { login, loading } = useAuth();
const handleLogin = () => {
login('user@example.com', 'password123');
};
return (
<button onClick={handleLogin} disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
);
}
function UserProfile() {
const { user, logout } = useAuth();
if (!user) return <p>Please login</p>;
return (
<div>
<p>Welcome, {user.name}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
When to Use useContext?
✅ USE useContext when:
Multiple components need the same data
You're passing props through 3+ levels (prop drilling)
You have global data (theme, user auth, language)
Many components need to update the same state
❌ DON'T use useContext for:
Data used by only parent-child components (just use props)
Data that changes very frequently (performance issues)
Simple state that's only used in one component
Common Mistakes
Mistake 1: Forgetting the Provider
// ❌ WRONG: Component outside Provider gets undefined
function Component() {
const value = useContext(MyContext); // value = undefined
return <p>{value}</p>;
}
// ✅ CORRECT: Wrap in Provider
function App() {
return (
<MyContext.Provider value="Hello">
<Component />
</MyContext.Provider>
);
}
Mistake 2: Unnecessary Rerenders
// ❌ WRONG: New object every render causes rerenders
function App() {
const [user, setUser] = useState({ name: 'John' });
return (
<UserContext.Provider value={{ user, setUser }}>
<Child />
</UserContext.Provider>
);
// New {user, setUser} object every time = Child rerenders every time
}
// ✅ BETTER: Memoize the value
function App() {
const [user, setUser] = useState({ name: 'John' });
const contextValue = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={contextValue}>
<Child />
</UserContext.Provider>
);
}
Practice Exercise: Build a Notification System
Create a notification system where:
Any component can show notifications
A NotificationCenter component displays all notifications
Notifications auto-remove after 3 seconds
// Try this structure:
const NotificationContext = createContext();
function NotificationProvider({ children }) {
const [notifications, setNotifications] = useState([]);
const addNotification = (message) => {
// Your code here
};
const removeNotification = (id) => {
// Your code here
};
return (
<NotificationContext.Provider value={{ addNotification }}>
{children}
<NotificationCenter notifications={notifications} />
</NotificationContext.Provider>
);
}
// Then use in components:
function AddToCartButton() {
const { addNotification } = useContext(NotificationContext);
const handleClick = () => {
// Add item to cart
addNotification('Item added to cart!');
};
return <button onClick={handleClick}>Add to Cart</button>;
}
Summary Cheat Sheet
// 1. CREATE context (usually in separate file)
const MyContext = createContext(defaultValue);
// 2. PROVIDE value (wrap your app/components)
<MyContext.Provider value={someValue}>
<ChildComponents />
</MyContext.Provider>
// 3. CONSUME value (in any child component)
const value = useContext(MyContext);
// 4. Best Practice: Create custom hook
function useMyContext() {
return useContext(MyContext);
}
Remember: useContext is like a shortcut through the component tree. It lets deeply nested components access data without every middle component needing to pass it along!