🌐 React Context API — Sharing State Without Prop Drilling
Master the React Context API. Avoid prop drilling, share theme/auth/locale state across the tree, and learn when Context is the wrong tool — with examples.
Prop drilling = passing props through many layers just so a deep child can read them. Tedious and noisy.
Context lets you put a value at the top of the tree and read it anywhere below — no manual passing.
🧪 The 3-step recipe
// 1. Create
const ThemeContext = createContext('light');
// 2. Provide
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
// 3. Consume
const theme = useContext(ThemeContext);🧠 Good use cases
- Theme (light/dark)
- Current user / auth
- Locale / i18n
- Feature flags
⚠️ Not for everything
Every consumer re-renders when the context value changes. So:
- Don't use one giant context for all app state.
- Do split contexts by concern, or pair with reducers/state libs (Zustand, Jotai, Redux).
- Do memoize the value object:
value={useMemo(() => ({user, login, logout}), [user])}
💻 Code Examples
Auth context with custom hook
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(
() => ({ user, login: setUser, logout: () => setUser(null) }),
[user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be inside AuthProvider');
return ctx;
}Output:
Any descendant calls `const { user, logout } = useAuth()`.Theme toggle with context
const ThemeContext = createContext({ theme: 'light', toggle: () => {} });
function App() {
const [theme, setTheme] = useState('light');
const toggle = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggle }}>
<Page />
</ThemeContext.Provider>
);
}Output:
Any nested component can read theme and call toggle().
⚠️ Common Mistakes
- Creating context value as a fresh object every render — causes ALL consumers to re-render every time.
- Putting unrelated state into one mega-context — every update re-renders everything.
- Forgetting to wrap consumers in a Provider — useContext returns the default value, often the wrong type.
- Reaching for Context too early when a parent component prop would do.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What problem does the Context API solve?Beginner
Prop drilling — having to thread the same prop through many layers of components just so a deep descendant can use it. Context lets any descendant read a value placed at an ancestor.
Q2.When should you NOT use Context?Intermediate
When state changes frequently and affects many components (Context re-renders every consumer). Reach for a state lib (Zustand, Redux, Jotai) or split contexts by concern.
Q3.How do you prevent every consumer from re-rendering on every change?Intermediate
Split into multiple smaller contexts, memoize the value object with useMemo, or move the changing state to a dedicated state library with selector subscriptions.
Q4.What does useContext return when there's no Provider above?Intermediate
The default value you passed to createContext(). To enforce that a Provider must exist, wrap useContext in a custom hook that throws if the value is null/undefined.
Q5.Context vs Redux — when to choose which?Advanced
Context is for low-frequency, simple shared data (theme, auth, locale). Redux/Zustand/Jotai are for complex, frequently-changing global state with selectors, middleware and devtools. They aren't competitors — they solve different problems.
🧠 Quick Summary
- Context = ancestor value readable by any descendant.
- createContext → Provider → useContext.
- Great for theme, auth, locale, flags.
- Bad for hot, frequently-changing app state.
- Always memoize the provided value object.