Context Is Not Global State (Please Stop That)
Context in React is one of those things that feels like a silver bullet when you first discover it. I remember the moment it clicked for me—I was knee-deep in a prop-drilling nightmare, eight levels deep, and frustrated out of my mind. Then someone said, “Just use context,” like they were handing me the keys to the kingdom. But like many devs, I overused it, misused it, and turned a clean app into a slow, unpredictable mess. So, let’s talk about what React Context is actually good for, where it fits in the state management world, and how to use it without tanking your performance.
🔍 What Context Is Actually For (Not Everything)
Context is designed for passing data deeply through the component tree without threading props through every single level. It’s ideal for things like themes, locales, user settings—stuff that’s app-wide and rarely changes. Not your live-updating chat messages or real-time game scores.
Here’s the classic example:
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
Notice how ThemedButton
gets the theme
value, even though Toolbar
didn’t pass it as a prop? That’s the magic.
But here’s what it’s not for: passing frequently updated state that triggers re-renders across huge parts of your app. If your context value changes every second, congratulations—you’ve accidentally created a performance trap.
⚔️ Context vs Redux vs Zustand vs Signals — The Cage Match
Now here’s where people get opinionated—and I will too.
Redux is amazing for large-scale state with devtools, middlewares, and team-scale control. But it’s verbose. Zustand is a beautifully simple state store. No boilerplate. Minimal API. If I’m not using context for app state, I’m using Zustand. Signals (like in Preact or Solid) introduce fine-grained reactivity. Think: change detection at the property level, not the component level. It’s fast—but it’s not idiomatic React (yet). Context shines when you need to provide config, user info, or app-wide constants—and nothing more.
Let me put it like this: If you’re using Context like a global store... you might be writing 2020 code in a 2025 world.
⚠️ The Perf Implications of Context — What They Don't Tell You
Here’s the thing that bit me hard: Context causes all consumers to re-render when the value changes—even if they don’t care about the specific change.
Imagine you’ve got:
<SettingsContext.Provider value={{ theme, language }}>
Now your ThemeSwitcher
and LanguagePicker
both consume the same object. Change the theme
, and guess what? LanguagePicker
re-renders too. Oops.
Solution? Split your providers:
<ThemeContext.Provider value={theme}>
<LanguageContext.Provider value={language}>
<App />
</LanguageContext.Provider>
</ThemeContext.Provider>
It looks ugly, but it saves you from triggering unnecessary renders. Also, memoize your context values. A changing reference—even if the data is the same—will force a re-render.
const value = useMemo(() => ({ theme }), [theme]);
Want more performance tips like this? Kent C. Dodds has a solid post on using context effectively without ruining perf.
🎨 Use Case: Theme and Language Providers (The Perfect Match)
Let’s get practical. Things like themes, languages, auth tokens—these are great candidates for context. They’re app-wide, rarely change, and they affect the UI or logic across many components.
Example:
const LanguageContext = createContext('en');
function Greeting() {
const lang = useContext(LanguageContext);
return <h1>{lang === 'en' ? 'Hello' : 'Hola'}</h1>;
}
What’s important here is that the frequency of change is low. The value might change once when the user switches language—maybe once per session. That’s ideal. No performance headaches.
Just don’t go wrapping your entire Redux-like store into a context and then wonder why half your app feels sluggish. You’ve been warned.
🚫 Don’t Abuse It: Know Its Limits (Seriously)
Here’s the mistake: Developers start with a simple context—say, for theme—and next thing you know, they’re stuffing it with user state, loading indicators, active modals, and sidebar visibility.
Now every time you toggle the sidebar, your whole app re-renders. You cry, your users cry, and React DevTools looks like a Christmas tree.
Use context for what it’s made for: static or infrequently updated data that many components need. If the state changes often or needs to be updated in slices? Go with Zustand, Redux, or even useReducer in isolated components.
Also worth checking: the `useContextSelector` library for fine-grained context selection. We’ll dive deeper into that in Performance Pitfalls of Context (and How to Dodge Them)—where we talk re-renders, memoization, and the smart patterns that make context usable without regret.
Ready to see how context can silently wreck your app’s performance if you’re not careful? Head over to the next lesson: **Performance Pitfalls of Context (and How to Dodge Them)**.