React Context API
Share global state without prop drilling
createContext
Provider
useContext
useReducer
1. Introduction: What is the Context API?
React Context API is a built-in feature that allows you to share data across your entire component tree without manually passing props down through every level.
Analogy: Context is like broadcast radio. Instead of calling every person individually (prop drilling), you broadcast the information, and anyone who wants to listen can tune in (consume the context).
JSX // WITHOUT Context (Prop Drilling)
<Dashboard user={user}>
<Profile user={user}>
<Avatar user={user} /> {/* user passed through 3 levels! */}
</Profile>
</Dashboard>
// WITH Context (Direct Access)
<UserProvider value={user}>
<Dashboard>
<Profile>
<Avatar /> {/* Directly consumes context */}
</Profile>
</Dashboard>
</UserProvider>
Built into React (no extra libraries)
Solves prop drilling
Great for medium-sized apps
Can be combined with useReducer for complex state
2. The Problem Context Solves (Prop Drilling)
2.1 What is Prop Drilling?
Prop drilling is passing data through multiple layers of components that don't need the data, just to get it to a deeply nested component that does.
2.2 The Prop Drilling Problem Visualized
JSX function App() {
const [user, setUser] = useState({ name: "Alice", theme: "dark" });
return (
<div>
<Header user={user} />
<MainContent user={user} />
<Footer user={user} />
</div>
);
}
function Header({ user }) {
return <header><NavBar user={user} /></header>;
}
function NavBar({ user }) {
return <nav><UserMenu user={user} /></nav>;
}
function UserMenu({ user }) {
return <div>Welcome, {user.name}!</div>; // Finally uses user!
}
2.3 Why Prop Drilling is a Problem
Problem Description
Code Duplication Every intermediate component must declare and pass props
Poor Maintainability Changing data structure requires updating many components
Reduced Reusability Components become coupled to parent data structure
Difficult Refactoring Moving a component requires updating all ancestors
Messy Signatures Components have props they don't even use
3. Core Concepts of Context API
3.1 createContext – Creating a Context
JSX import { createContext } from 'react';
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const SettingsContext = createContext({
language: 'en',
notifications: true,
fontSize: 'medium'
});
// MyContext contains:
// - MyContext.Provider
// - MyContext.Consumer (legacy)
3.2 Provider – Providing the Value
JSX function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<MainContent />
<Footer />
</ThemeContext.Provider>
);
}
Important: When the Provider's value changes, all consumers re-render.
3.3 useContext – Consuming the Value
JSX import { useContext } from 'react';
import { ThemeContext } from './contexts';
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{ background: theme === 'dark' ? '#333' : '#fff' }}
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
Toggle Theme
</button>
);
}
3.4 Context.Consumer (Legacy Way)
JSX function ThemedButton() {
return (
<ThemeContext.Consumer>
{({ theme, setTheme }) => (
<button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
{theme}
</button>
)}
</ThemeContext.Consumer>
);
}
4. Step-by-Step Guide to Using Context
4.1 Step 1: Create a Context
JSX // contexts/ThemeContext.jsx
import { createContext } from 'react';
export const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
});
4.2 Step 2: Create a Provider Component
JSX export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
const value = { theme, toggleTheme, setTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
4.3 Step 3: Wrap the App with Provider
JSX createRoot(document.getElementById('root')).render(
<ThemeProvider>
<App />
</ThemeProvider>
);
4.4 Step 4: Consume Context with useContext
JSX function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
Current Theme: {theme}
</button>
);
}
5. Multiple Contexts
5.1 Using Multiple Independent Contexts
JSX function App() {
return (
<ThemeProvider>
<UserProvider>
<CartProvider>
<Dashboard />
</CartProvider>
</UserProvider>
</ThemeProvider>
);
}
function Dashboard() {
const { theme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
const { cart } = useContext(CartContext);
return (
<div className={theme}>
<h1>Welcome, {user.name}</h1>
<p>Cart items: {cart.length}</p>
</div>
);
}
5.2 Nesting Context Providers
JSX function App() {
return (
<ThemeProvider> {/* Global theme */}
<Header />
<UserProvider> {/* Authenticated section */}
<MainContent />
<SidebarProvider> {/* Sidebar-specific state */}
<Sidebar />
</SidebarProvider>
</UserProvider>
<Footer />
</ThemeProvider>
);
}
6. Context with Complex State
6.1 Combining Context with useReducer
JSX const initialState = { items: [], total: 0, itemCount: 0 };
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find(i => i.id === action.payload.id);
const items = existing
? state.items.map(i => i.id === action.payload.id
? { ...i, quantity: i.quantity + 1 } : i)
: [...state.items, { ...action.payload, quantity: 1 }];
return {
...state,
items,
total: items.reduce((s, i) => s + i.price * i.quantity, 0),
itemCount: items.reduce((s, i) => s + i.quantity, 0)
};
}
case 'REMOVE_ITEM': {
const items = state.items.filter(i => i.id !== action.payload);
return { ...state, items, total: 0, itemCount: 0 };
}
case 'CLEAR_CART':
return initialState;
default:
return state;
}
}
const CartContext = createContext();
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
const value = {
cart: state,
addItem: (item) => dispatch({ type: 'ADD_ITEM', payload: item }),
removeItem: (id) => dispatch({ type: 'REMOVE_ITEM', payload: id }),
clearCart: () => dispatch({ type: 'CLEAR_CART' })
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
export function useCart() {
const context = useContext(CartContext);
if (!context) throw new Error('useCart must be used within CartProvider');
return context;
}
6.2 Creating a Redux-like Store with Context
JSX const initialState = {
user: null, theme: 'light', notifications: [], loading: false
};
function rootReducer(state, action) {
switch (action.type) {
case 'SET_USER': return { ...state, user: action.payload };
case 'LOGOUT': return { ...state, user: null };
case 'SET_THEME': return { ...state, theme: action.payload };
default: return state;
}
}
export function StoreProvider({ children }) {
const [state, dispatch] = useReducer(rootReducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
export function useSelector(selector) {
const { state } = useStore();
return selector(state);
}
function UserProfile() {
const user = useSelector(state => state.user);
const dispatch = useDispatch();
return <div>{user?.name} <button onClick={() => dispatch({ type: 'LOGOUT' })}>Logout</button></div>;
}
7. Complete Working Examples
7.1 Example 1: Theme Switcher (Simple Context)
JSX export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({
theme,
toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light')
}), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
function AppShell() {
const { theme, toggleTheme } = useTheme();
return (
<div className={theme}>
<button onClick={toggleTheme}>Switch to {theme === 'light' ? 'dark' : 'light'}</button>
</div>
);
}
7.2 Example 2: User Authentication (Medium Complexity)
JSX export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
checkSession().then(setUser).finally(() => setLoading(false));
}, []);
const login = async (email, password) => {
const profile = await apiLogin(email, password);
setUser(profile);
};
const logout = () => setUser(null);
const value = useMemo(() => ({ user, loading, login, logout }), [user, loading]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) return <p>Loading...</p>;
if (!user) return <Navigate to="/login" />;
return children;
}
7.3 Example 3: Shopping Cart with Reducer (Complex State)
JSX function ProductPage({ product }) {
const { addItem, cart } = useCart();
return (
<div>
<h2>{product.name}</h2>
<button onClick={() => addItem(product)}>Add to Cart</button>
<p>Items in cart: {cart.itemCount}</p>
</div>
);
}
function CartSummary() {
const { cart, removeItem, clearCart } = useCart();
return (
<div>
<h3>Total: ${cart.total}</h3>
{cart.items.map(item => (
<div key={item.id}>
{item.name} x{item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<button onClick={clearCart}>Clear Cart</button>
</div>
);
}
7.4 Example 4: Multi-Language Support (Multiple Contexts)
JSX const translations = { en: { welcome: 'Welcome' }, es: { welcome: 'Bienvenido' } };
export function LanguageProvider({ children }) {
const [lang, setLang] = useState('en');
const t = (key) => translations[lang][key];
const value = useMemo(() => ({ lang, setLang, t }), [lang]);
return <LanguageContext.Provider value={value}>{children}</LanguageContext.Provider>;
}
function Greeting() {
const { t, lang, setLang } = useLanguage();
return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => setLang(lang === 'en' ? 'es' : 'en')}>Toggle Language</button>
</div>
);
}
8. Context vs Other State Management Solutions
Aspect Context API Redux Zustand Jotai
Learning Curve Low (built-in) High Medium Medium
Boilerplate Minimal High Low Low
Performance Good (with memo) Excellent Excellent Excellent
DevTools Basic Excellent Good Good
Bundle Size 0 (built-in) ~10KB ~3KB ~3KB
Best For Small to medium apps Large apps All sizes All sizes
Use Context when: small to medium apps, simple shared state (theme, auth, language), avoiding prop drilling, no extra dependencies.
Don't use Context when: high-frequency updates, complex state logic without careful splitting, time-travel debugging needs, very large complex apps.
9. Common Mistakes and Best Practices
Mistake Problem Solution
Missing Provider useContext returns undefinedAlways wrap app with Provider
Unstable value object Unnecessary re-renders Memoize value with useMemo
Overusing Context Performance issues Use only for truly global state
Mutating context value React doesn't detect changes Create new objects/arrays
One giant context Tight coupling Split contexts by concern
JSX // DO
const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
// DON'T
<MyContext.Provider value={{ user, setUser }}> // new object every render
user.name = 'New Name'; // mutation!
10. Summary Cheat Sheet
JSX // CREATING CONTEXT
const MyContext = createContext(defaultValue);
// PROVIDER
<MyContext.Provider value={value}>{children}</MyContext.Provider>
// CONSUMING
const value = useContext(MyContext);
// COMPLETE PATTERN
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}
// MULTIPLE CONTEXTS
<ThemeProvider>
<UserProvider>
<CartProvider><App /></CartProvider>
</UserProvider>
</ThemeProvider>
// CONTEXT WITH REDUCER
const [state, dispatch] = useReducer(reducer, initialState);
<StoreContext.Provider value={{ state, dispatch }}>{children}</StoreContext.Provider>
// QUICK REFERENCE
// createContext() → Creates context object
// .Provider → Provides value to tree
// useContext() → Consumes value in component
// Custom hook → Best practice for consuming
// Memoize value → Prevents unnecessary re-renders
// Split contexts → Better performance and organization
React Context API MCQ Practice
10 Basic MCQs
10 Advanced MCQs
10 Basic React Context API MCQs
1
Context solves:
A Prop drilling for shared data
B CSS bundling
C npm installs
D SQL queries
Explanation: Provide value to deep tree without every level passing props.
2
createContext returns:
A Context object with Provider and Consumer
B Redux store
C Router
D DOM node
Explanation: Pair with Provider and useContext.
3
Provider wraps:
A Subtree that should receive value
B Only one button
C node_modules
D vite.config
Explanation: <ThemeContext.Provider value={theme}>.
4
useContext(ThemeContext) returns:
A Current value from nearest Provider
B Always null
C props of parent
D CSS file
Explanation: Reads context value for this component.
5
Default value in createContext is used when:
A No matching Provider above
B Always overrides Provider
C On npm install
D Never
Explanation: Fallback when no Provider in tree.
6
Context value change causes:
A All consuming components to re-render
B No renders ever
C Page reload
D Only Provider re-render
Explanation: Consumers subscribe to context updates.
7
Multiple contexts can:
A Coexist in same app
B Only one allowed
C Replace useState globally
D Disable hooks
Explanation: Theme, Auth, Locale separate contexts.
8
Passing object in Provider value={{user, setUser}} without memo:
A New reference each render may re-render all consumers
B Always optimal
C Forbidden
D SSR only
Explanation: Memoize value object when needed.
9
Context is best for:
A Low-frequency global data like theme/auth
B Every keystroke local input
C Replacing all state
D Binary files
Explanation: Not for high-frequency fine-grained updates.
10
Consumer component (legacy) vs useContext:
A useContext is modern hook approach
B Consumer is only way in React 18
C Neither works in functions
D Both removed
Explanation: Prefer useContext in function components.
10 Advanced React Context API MCQs
1
Splitting context by concern reduces:
A Unnecessary re-renders
B Bundle size to zero
C Need for components
D HTML
Explanation: AuthContext vs ThemeContext decouple updates.
2
useMemo on Provider value prevents:
A New object identity every parent render
B All context usage
C Children mounting
D Hooks
Explanation: const v = useMemo(() => ({...}), [deps]).
3
Context + useReducer pattern mimics:
A Lightweight global store
B SQL database
C CSS modules
D Router only
Explanation: dispatch and state via context.
4
Server Components note:
A Client context does not apply on server the same way
B Context banned everywhere
C Only classes
D No React 18
Explanation: RSC has different data passing model.
5
Testing components using context:
A Wrap render in Provider with test value
B Skip Provider
C Use only Enzyme
D Cannot test
Explanation: RTL: render(<Provider><Ui /></Provider>).
6
Dynamic context value updates when:
A Provider value prop changes
B Child mutates context directly
C npm updates
D Never
Explanation: Children cannot mutate context—update via Provider.
7
Compound component + context often:
A Shares internal state without exporting all props
B Replaces DOM
C Uses jQuery
D Deletes children
Explanation: Tabs, Accordion patterns.
8
Context vs Redux:
A Redux adds devtools, middleware, structured updates; Context is built-in simpler
B Identical always
C Context replaces Redux always
D Redux is built into React
Explanation: Choose based on complexity and tooling needs.
9
Forwarding refs separate from context:
A forwardRef for DOM ref; context for data
B Same API
C Cannot combine
D Forbidden
Explanation: Different problems.
10
Performance: context with rapidly changing value:
A May re-render large subtree—consider state colocation or external store
B Always fastest
C Disables memo
D Only SSR issue
Explanation: Profile and narrow Provider scope.
Check All Answers
15 React Context API Interview Questions & Answers
Easy
Medium
Hard
1 What problem does Context solve?easy
Answer: Sharing data across components without passing props at every level.
2 How to create and use context?easy
Answer: createContext, wrap Provider, read with useContext in descendants.
3 What is prop drilling?medium
Answer: Passing props through many layers that do not need them.
4 When NOT to use Context?medium
Answer: For frequently changing fine-grained state or as replacement for all local state.
5 How to optimize Context performance?hard
Answer: Split contexts, memoize Provider value, colocate state.
6 Default context value purpose?medium
Answer: Fallback for components rendered outside Provider (e.g. tests).
7 Can context replace Redux?medium
Answer: For simple apps yes; complex apps may still need Redux/Zustand.
8 useContext subscription behavior?medium
Answer: Component re-renders when context value it reads changes.
9 Multiple providers nested?easy
Answer: Inner Provider overrides value for its subtree.
10 Custom hook with context example?medium
Answer: function useAuth() { return useContext(AuthContext); }
11 Context and TypeScript?hard
Answer: createContext<Type | null>(null) with null checks or default.
12 Provider value as inline object issue?hard
Answer: New reference each render can re-render all consumers.
13 Context vs composition?hard
Answer: Sometimes pass JSX children instead of context for flexibility.
14 Testing with mock Provider?medium
Answer: Supply test doubles for auth user, theme, etc.
15 Security: context for auth?hard
Answer: OK for client UX; never store secrets—validate on server.