useContext & Context API
EasyuseContext 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