useContext
React relies on a one-way data flow: passing data from parent to child via props. However, when data needs to be accessed by many components at different nesting levels, passing props down the tree (prop drilling) becomes tedious and messy.
The Context API solves this, and the useContext hook is the modern way to consume it.
The Problem: Prop Drilling
Imagine passing a theme or user object through 5 layers of components that don't even use the data, just to get it to a deeply nested button.
The Solution: Context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Step 1: Create the Context
Create a context outside of your components so it can be exported and imported where needed.
import { createContext } from 'react';
// Create a context with a default value (optional)
export const ThemeContext = createContext('light');
Step 2: Provide the Context
Use the Context's Provider component to wrap the part of your app that needs access to the data.
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import Layout from './Layout';
function App() {
const [theme, setTheme] = useState('dark');
return (
// Any component inside Provider can access the 'value'
<ThemeContext.Provider value={theme}>
<Layout />
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
</ThemeContext.Provider>
);
}
Step 3: Consume the Context
Any component underneath the Provider can use the useContext hook to access the value.
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton() {
// Call useContext with the Context object created in Step 1
const theme = useContext(ThemeContext);
const className = theme === 'dark' ? 'bg-black text-white' : 'bg-white text-black';
return <button className={className}>Click Me!</button>;
}
Common Use Cases
Context is excellent for global or semi-global state that changes infrequently:
- Theming (dark mode vs. light mode)
- User Authentication (current logged-in user profile)
- Localization (current language or timezone)
- Routing (accessing the current URL/router state)
Performance Considerations
Whenever the value prop of a Provider changes, every single component consuming that context will re-render.
If you store a large, complex object in Context and only one property updates, all components listening to that context will re-render, even if they don't care about that specific property.
Best Practices to prevent unnecessary re-renders:
- Split Contexts: Instead of one giant
<AppContext>, have a<ThemeContext>and an<AuthContext>. - Memoize Provider Values: If the value is an object or array, wrap it in
useMemoso the reference doesn't change on every parent render.
const contextValue = useMemo(() => ({ theme, toggleTheme }), [theme]);
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
- Use state management for high-frequency updates: If your data updates multiple times a second (e.g., mouse tracking, live chat), Context is not the right tool. Consider Zustand, Redux, or Jotai instead.