useContext Hook
The useContext hook lets you subscribe to React context and access values from any component without prop drilling.
The Problem: Prop Drilling
jsx
1// Without context - passing props through many levels
2function App() {
3 const [theme, setTheme] = useState('dark')
4 return <Header theme={theme} setTheme={setTheme} />
5}
6
7function Header({ theme, setTheme }) {
8 return <Navigation theme={theme} setTheme={setTheme} />
9}
10
11function Navigation({ theme, setTheme }) {
12 return <ThemeToggle theme={theme} setTheme={setTheme} />
13}
14
15function ThemeToggle({ theme, setTheme }) {
16 // Finally use the props!
17 return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme}</button>
18}The Solution: Context
Step 1: Create Context
jsx
1import { createContext } from 'react'
2
3// Create with default value
4const ThemeContext = createContext('light')
5
6// Or without default (useful when value is always provided)
7const ThemeContext = createContext(null)
8
9export default ThemeContextStep 2: Provide Context
jsx
1import { useState } from 'react'
2import ThemeContext from './ThemeContext'
3
4function App() {
5 const [theme, setTheme] = useState('dark')
6
7 return (
8 <ThemeContext.Provider value={{ theme, setTheme }}>
9 <Header />
10 </ThemeContext.Provider>
11 )
12}Step 3: Consume Context
jsx
1import { useContext } from 'react'
2import ThemeContext from './ThemeContext'
3
4function ThemeToggle() {
5 const { theme, setTheme } = useContext(ThemeContext)
6
7 return (
8 <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
9 Current theme: {theme}
10 </button>
11 )
12}Complete Example: Theme Context
jsx
1// ThemeContext.jsx
2import { createContext, useContext, useState } from 'react'
3
4const ThemeContext = createContext(null)
5
6export function ThemeProvider({ children }) {
7 const [theme, setTheme] = useState('light')
8
9 const toggleTheme = () => {
10 setTheme(prev => prev === 'light' ? 'dark' : 'light')
11 }
12
13 return (
14 <ThemeContext.Provider value={{ theme, toggleTheme }}>
15 {children}
16 </ThemeContext.Provider>
17 )
18}
19
20export function useTheme() {
21 const context = useContext(ThemeContext)
22 if (!context) {
23 throw new Error('useTheme must be used within ThemeProvider')
24 }
25 return context
26}
27
28// App.jsx
29import { ThemeProvider } from './ThemeContext'
30
31function App() {
32 return (
33 <ThemeProvider>
34 <Header />
35 <Main />
36 <Footer />
37 </ThemeProvider>
38 )
39}
40
41// Any nested component
42import { useTheme } from './ThemeContext'
43
44function ThemeToggle() {
45 const { theme, toggleTheme } = useTheme()
46
47 return (
48 <button onClick={toggleTheme}>
49 Switch to {theme === 'light' ? 'dark' : 'light'} mode
50 </button>
51 )
52}Authentication Context
jsx
1// AuthContext.jsx
2import { createContext, useContext, useState, useEffect } from 'react'
3
4const AuthContext = createContext(null)
5
6export function AuthProvider({ children }) {
7 const [user, setUser] = useState(null)
8 const [loading, setLoading] = useState(true)
9
10 useEffect(() => {
11 // Check for existing session
12 const checkAuth = async () => {
13 try {
14 const response = await fetch('/api/auth/me')
15 if (response.ok) {
16 const userData = await response.json()
17 setUser(userData)
18 }
19 } finally {
20 setLoading(false)
21 }
22 }
23 checkAuth()
24 }, [])
25
26 const login = async (email, password) => {
27 const response = await fetch('/api/auth/login', {
28 method: 'POST',
29 body: JSON.stringify({ email, password })
30 })
31 const userData = await response.json()
32 setUser(userData)
33 return userData
34 }
35
36 const logout = async () => {
37 await fetch('/api/auth/logout', { method: 'POST' })
38 setUser(null)
39 }
40
41 return (
42 <AuthContext.Provider value={{ user, loading, login, logout }}>
43 {children}
44 </AuthContext.Provider>
45 )
46}
47
48export function useAuth() {
49 const context = useContext(AuthContext)
50 if (!context) {
51 throw new Error('useAuth must be used within AuthProvider')
52 }
53 return context
54}
55
56// Usage
57function Profile() {
58 const { user, logout, loading } = useAuth()
59
60 if (loading) return <p>Loading...</p>
61 if (!user) return <p>Please log in</p>
62
63 return (
64 <div>
65 <h1>Welcome, {user.name}</h1>
66 <button onClick={logout}>Logout</button>
67 </div>
68 )
69}Multiple Contexts
jsx
1function App() {
2 return (
3 <AuthProvider>
4 <ThemeProvider>
5 <CartProvider>
6 <Main />
7 </CartProvider>
8 </ThemeProvider>
9 </AuthProvider>
10 )
11}
12
13// Component using multiple contexts
14function Header() {
15 const { user } = useAuth()
16 const { theme } = useTheme()
17 const { itemCount } = useCart()
18
19 return (
20 <header className={theme}>
21 <span>Welcome, {user?.name}</span>
22 <span>Cart: {itemCount} items</span>
23 </header>
24 )
25}Context with TypeScript
tsx
1import { createContext, useContext, useState, ReactNode } from 'react'
2
3interface User {
4 id: string
5 name: string
6 email: string
7}
8
9interface AuthContextType {
10 user: User | null
11 login: (email: string, password: string) => Promise<void>
12 logout: () => void
13 loading: boolean
14}
15
16const AuthContext = createContext<AuthContextType | null>(null)
17
18interface AuthProviderProps {
19 children: ReactNode
20}
21
22export function AuthProvider({ children }: AuthProviderProps) {
23 const [user, setUser] = useState<User | null>(null)
24 const [loading, setLoading] = useState(true)
25
26 const login = async (email: string, password: string) => {
27 // login logic
28 }
29
30 const logout = () => {
31 setUser(null)
32 }
33
34 return (
35 <AuthContext.Provider value={{ user, login, logout, loading }}>
36 {children}
37 </AuthContext.Provider>
38 )
39}
40
41export function useAuth(): AuthContextType {
42 const context = useContext(AuthContext)
43 if (!context) {
44 throw new Error('useAuth must be used within AuthProvider')
45 }
46 return context
47}Optimizing Context
Split contexts to avoid unnecessary re-renders:
jsx
1// ❌ All consumers re-render when any value changes
2const AppContext = createContext({
3 user: null,
4 theme: 'light',
5 language: 'en'
6})
7
8// ✅ Separate contexts for unrelated values
9const UserContext = createContext(null)
10const ThemeContext = createContext('light')
11const LanguageContext = createContext('en')Default Values
jsx
1// Default used when no Provider is above
2const ThemeContext = createContext({
3 theme: 'light',
4 toggleTheme: () => {
5 console.warn('No ThemeProvider found')
6 }
7})
8
9// Now components work even without Provider
10function ThemedButton() {
11 const { theme } = useContext(ThemeContext)
12 return <button className={theme}>Click me</button>
13}Context is powerful for sharing global state across your app!
