useContext & Context API

Easy

useContext reads a value from a React Context, letting you pass data through the component tree without prop drilling. A Provider component supplies the value, and any descendant that calls useContext receives it. Context is ideal for global or semi-global state like themes, auth, or locale.

Interactive Visualization

Key Points

  • createContext creates a context with an optional default value
  • Provider wraps a subtree and supplies the context value
  • useContext reads the nearest Provider value above in the tree
  • All consumers re-render when the Provider value changes
  • Split large contexts into smaller focused ones to reduce unnecessary re-renders
  • Custom hooks wrapping useContext provide better error messages and abstraction

Code Examples

Theme Context

import { createContext, useContext, useState, type ReactNode } from 'react'

interface ThemeContextType {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const ThemeContext = createContext<ThemeContextType | null>(null)

function useTheme(): ThemeContextType {
  const ctx = useContext(ThemeContext)
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
  return ctx
}

function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  const toggleTheme = () => setTheme((t) => t === 'light' ? 'dark' : 'light')

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

function ThemeButton() {
  const { theme, toggleTheme } = useTheme()
  return <button onClick={toggleTheme}>Current: {theme}</button>
}

A custom hook wraps useContext with a null check, providing a clean API and helpful error if used outside the provider

Avoiding Re-renders with Split Contexts

import { createContext, useContext, useState, useMemo, type ReactNode } from 'react'

interface UserContextType {
  user: string | null
  setUser: (u: string | null) => void
}

interface ThemeContextType {
  theme: string
  setTheme: (t: string) => void
}

const UserContext = createContext<UserContextType | null>(null)
const ThemeContext = createContext<ThemeContextType | null>(null)

function AppProviders({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<string | null>(null)
  const [theme, setTheme] = useState('light')

  const userValue = useMemo(() => ({ user, setUser }), [user])
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme])

  return (
    <UserContext.Provider value={userValue}>
      <ThemeContext.Provider value={themeValue}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  )
}

Splitting context and memoizing values ensures that theme changes do not re-render user-only consumers and vice versa

Common Mistakes

  • Putting everything in a single context causing all consumers to re-render on any change
  • Not memoizing the context value object, which creates a new reference every render
  • Using context for high-frequency updates like mouse position that should use state or refs

Interview Tips

  • Explain the re-render behavior: all consumers re-render when the provider value changes
  • Discuss strategies to mitigate context re-renders: splitting, memoizing, selectors
  • Know when to use context vs a state management library like Zustand

Related Concepts