Context API and Global State
Passing props down through multiple layers of components to reach a deeply nested child is called Prop Drilling. This clutters intermediate components with unused props. React Context solves this by broadcasting data directly to any consuming component in the tree.
1. Creating and Using Context
To use Context, follow three simple steps:
- Create: Declare a Context object using
createContext. - Provide: Wrap parent components in the Context Provider to broadcast data.
- Consume: Access the value inside any child using the
useContexthook.
import { createContext, useContext, useState } from "react";
// 1. Create Context
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
// 2. Provide value to children
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Consume Context in a nested child
export function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme} className={`btn-${theme}`}>
Current Mode: {theme}
</button>
);
}2. Best Practices for Context Setup
When setting up global state providers, structure them to avoid cluttering your root component:
- Export Custom Hooks: Instead of importing both the Context object and
useContexthook in every component, export a custom hook that wraps the logic.
// Custom hook to consume ThemeContext safely
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}3. Performance Pitfalls: Unnecessary Re-Renders
Whenever the value passed to Context.Provider changes, all components that consume that Context will re-render.
If you pass a combined object containing multiple state values, updates to any single property will trigger re-renders even in components that only consume the unchanged fields:
// Bad Performance Setup
<AppContext.Provider value={{ user, theme, preferences }}>Mitigation Strategies
- Split Contexts: Create dedicated providers for unrelated states (for example, keep
UserProviderseparate fromThemeProvider). - Memoize Values: Use
useMemoto cache the provider value object so it only changes when necessary.
Published on Last updated: