Skip
Arish's avatar

10. useContext Hook


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 ThemeContext

Step 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!