1. Introduction π
useContext solves one of React's most common structural challenges: passing data through many layers of components without manually threading props at every level. This tutorial covers everything from creating basic Context to advanced patterns like combining Context with reducers for global state management.
Information
2. What is Context? π€
Context provides a way to share values β like themes, authenticated user data, or language preferences β across a component tree without explicitly passing props through every intermediate component.
Code Snippet
import { createContext, useContext } from 'react';
const ThemeContext = createContext("light");
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click Me</button>;
}3. Why Use Context? π‘
- Avoids Prop Drilling: Eliminates passing props through components that don't need them.
- Global-Like State: Shares data such as themes, authentication, or locale across many components.
- Cleaner Component APIs: Intermediate components stay free of irrelevant pass-through props.
- Centralized Updates: Changes to shared data propagate automatically to all consumers.
4. Context vs Props βοΈ
| Aspect | Props | Context |
|---|---|---|
| Data Flow | Explicit, passed at each level | Implicit, available to any descendant |
| Best For | Direct parent-to-child communication | Data needed by many, deeply nested components |
| Traceability | Easy to trace data origin | Less explicit, requires knowing the Provider exists |
| Overuse Risk | Prop drilling in large trees | Hidden dependencies, harder to reuse components |
Best Practice
5. Creating Context π±
Code Snippet
import { createContext } from 'react';
const ThemeContext = createContext("light"); // "light" is the default valueNote
6. Context Provider π‘
A Context.Provider component makes a value available to all of its descendant components, regardless of nesting depth.
Code Snippet
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}Tip
7. Context Consumer π₯
The older Context.Consumer component reads context using a render prop pattern. It's largely superseded by useContext in modern function components.
Code Snippet
function ThemedButton() {
return (
<ThemeContext.Consumer>
{(theme) => <button className={theme}>Click Me</button>}
</ThemeContext.Consumer>
);
}Note
8. Using useContext πͺ
Code Snippet
import { useContext } from 'react';
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click Me</button>;
}Important
9. Providing Values π€
Code Snippet
function App() {
const [user, setUser] = useState({ name: "Alice", role: "admin" });
return (
<UserContext.Provider value={user}>
<Dashboard />
</UserContext.Provider>
);
}10. Consuming Values π₯
Code Snippet
function UserGreeting() {
const user = useContext(UserContext);
return <p>Welcome, {user.name}!</p>;
}Caution
11. Updating Context Values π
To let consumers update shared context data, pass both the value and its setter function together through the Provider.
Code Snippet
const ThemeContext = createContext(null);
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ThemeToggle />
</ThemeContext.Provider>
);
}
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Current: {theme}
</button>
);
}12. Multiple Contexts π§©
Components can consume multiple independent contexts by calling useContext more than once.
Code Snippet
function Dashboard() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
const language = useContext(LanguageContext);
return (
<div className={theme}>
<p>{language === "en" ? "Welcome" : "Bienvenue"}, {user.name}</p>
</div>
);
}13. Nested Context Providers πͺ
Providers can be nested β an inner Provider of the same context overrides the value for its own subtree, without affecting components outside it.
Code Snippet
function App() {
return (
<ThemeContext.Provider value="dark">
<Header /> {/* sees "dark" */}
<ThemeContext.Provider value="light">
<Sidebar /> {/* sees "light" */}
</ThemeContext.Provider>
</ThemeContext.Provider>
);
}14. Default Context Values π
The default value passed to createContext() acts as a fallback, useful for components rendered outside any Provider, or for improving TS type inference.
Code Snippet
const ThemeContext = createContext("light"); // fallback if no Provider is foundTip
15. Context with State π§
Code Snippet
const CartContext = createContext(null);
function CartProvider({ children }) {
const [items, setItems] = useState([]);
function addItem(item) {
setItems((prev) => [...prev, item]);
}
return (
<CartContext.Provider value={{ items, addItem }}>
{children}
</CartContext.Provider>
);
}
function CartSummary() {
const { items } = useContext(CartContext);
return <p>{items.length} items in cart</p>;
}16. Context with Reducers ποΈ
For more complex state logic, combining Context with useReducer creates a lightweight, Redux-like pattern for global state.
Code Snippet
function cartReducer(state, action) {
switch (action.type) {
case "add": return [...state, action.item];
case "remove": return state.filter((i) => i.id !== action.id);
default: return state;
}
}
function CartProvider({ children }) {
const [items, dispatch] = useReducer(cartReducer, []);
return (
<CartContext.Provider value={{ items, dispatch }}>
{children}
</CartContext.Provider>
);
}
function AddToCartButton({ item }) {
const { dispatch } = useContext(CartContext);
return <button onClick={() => dispatch({ type: "add", item })}>Add</button>;
}17. Global State Using Context π
Combining a Provider component with a custom Hook creates a clean, reusable API for global state, hiding the raw useContext call behind a friendlier interface.
Code Snippet
const AppStateContext = createContext(null);
export function AppStateProvider({ children }) {
const [state, setState] = useState({ theme: "light", user: null });
return (
<AppStateContext.Provider value={{ state, setState }}>
{children}
</AppStateContext.Provider>
);
}
export function useAppState() {
const context = useContext(AppStateContext);
if (!context) throw new Error("useAppState must be used within AppStateProvider");
return context;
}Best Practice
18. Theme Management π¨
Code Snippet
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={theme}>{children}</div>
</ThemeContext.Provider>
);
}Reference
19. Authentication Context π
Code Snippet
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
function login(credentials) {
// authenticate and setUser(...)
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
return useContext(AuthContext);
}Important
20. Language (i18n) Context π
Code Snippet
const LanguageContext = createContext("en");
function LanguageProvider({ children }) {
const [language, setLanguage] = useState("en");
return (
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
);
}
function Greeting() {
const { language } = useContext(LanguageContext);
const messages = { en: "Hello!", fr: "Bonjour!", es: "Β‘Hola!" };
return <p>{messages[language]}</p>;
}21. Context Performance β‘
Every component consuming a context re-renders whenever the Provider's value changes β even if the consumer only uses a small part of that value.
Problem: Every Consumer Re-renders on Any Change
<AppContext.Provider value={{ user, theme, notifications }}>
{/* Any change to ANY of these fields re-renders ALL consumers */}
</AppContext.Provider>Warning
22. Avoiding Unnecessary Re-renders π«
- Split contexts by concern (e.g., separate ThemeContext and UserContext) so unrelated updates don't affect unrelated consumers.
- Memoize the Provider's value object with useMemo to avoid creating a new reference on every render.
- Wrap consuming components in memo where appropriate.
Code Snippet
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}Danger
23. Context Composition π§¬
Multiple context Providers are often combined into a single wrapper component to keep the application's root cleaner and more maintainable.
Code Snippet
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
function App() {
return (
<AppProviders>
<Dashboard />
</AppProviders>
);
}24. TypeScript with useContext π·
Code Snippet
interface AuthContextValue {
user: User | null;
login: (credentials: Credentials) => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
function useAuth(): AuthContextValue {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}Tip
25. Best Practices π
- Use Context for genuinely global or widely-shared data, not as a default replacement for props.
- Split contexts by concern rather than combining unrelated data into one large context.
- Wrap useContext calls in custom Hooks for a cleaner API and built-in error checking.
- Memoize Provider value objects to avoid unnecessary re-renders.
- Keep Provider components focused β extract state logic into a dedicated Provider component, not inline in App.
26. Common Mistakes π«
- Using Context for every piece of shared state, even when simple prop passing would suffice.
- Passing a new inline object as the Provider's value on every render, causing unnecessary re-renders.
- Combining unrelated data into a single large context, causing excessive re-renders across the app.
- Forgetting to wrap components in the correct Provider, silently falling back to the default context value.
- Using Context for very high-frequency updates, where performance suffers compared to more targeted state solutions.
Danger
27. Frequently Asked Questions β
Question
Answer
Question
Answer
Question
Answer
28. Summary π
useContext solves prop drilling by letting components read shared values directly from a Provider anywhere above them in the tree. Combined with useState or useReducer, it forms a lightweight, built-in solution for themes, authentication, and other cross-cutting application state.