React Context API

Share global state without prop drilling

createContext Provider useContext useReducer

Table of Contents

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

JSXfunction 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

ProblemDescription
Code DuplicationEvery intermediate component must declare and pass props
Poor MaintainabilityChanging data structure requires updating many components
Reduced ReusabilityComponents become coupled to parent data structure
Difficult RefactoringMoving a component requires updating all ancestors
Messy SignaturesComponents have props they don't even use

3. Core Concepts of Context API

3.1 createContext – Creating a Context

JSXimport { 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

JSXfunction 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

JSXimport { 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)

JSXfunction 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

JSXexport 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

JSXcreateRoot(document.getElementById('root')).render( <ThemeProvider> <App /> </ThemeProvider> );

4.4 Step 4: Consume Context with useContext

JSXfunction 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

JSXfunction 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

JSXfunction 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

JSXconst 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

JSXconst 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)

JSXexport 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)

JSXexport 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)

JSXfunction 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)

JSXconst 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

AspectContext APIReduxZustandJotai
Learning CurveLow (built-in)HighMediumMedium
BoilerplateMinimalHighLowLow
PerformanceGood (with memo)ExcellentExcellentExcellent
DevToolsBasicExcellentGoodGood
Bundle Size0 (built-in)~10KB~3KB~3KB
Best ForSmall to medium appsLarge appsAll sizesAll 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

MistakeProblemSolution
Missing ProvideruseContext returns undefinedAlways wrap app with Provider
Unstable value objectUnnecessary re-rendersMemoize value with useMemo
Overusing ContextPerformance issuesUse only for truly global state
Mutating context valueReact doesn't detect changesCreate new objects/arrays
One giant contextTight couplingSplit 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:

AProp drilling for shared data
BCSS bundling
Cnpm installs
DSQL queries
Explanation: Provide value to deep tree without every level passing props.
2

createContext returns:

AContext object with Provider and Consumer
BRedux store
CRouter
DDOM node
Explanation: Pair with Provider and useContext.
3

Provider wraps:

ASubtree that should receive value
BOnly one button
Cnode_modules
Dvite.config
Explanation: <ThemeContext.Provider value={theme}>.
4

useContext(ThemeContext) returns:

ACurrent value from nearest Provider
BAlways null
Cprops of parent
DCSS file
Explanation: Reads context value for this component.
5

Default value in createContext is used when:

ANo matching Provider above
BAlways overrides Provider
COn npm install
DNever
Explanation: Fallback when no Provider in tree.
6

Context value change causes:

AAll consuming components to re-render
BNo renders ever
CPage reload
DOnly Provider re-render
Explanation: Consumers subscribe to context updates.
7

Multiple contexts can:

ACoexist in same app
BOnly one allowed
CReplace useState globally
DDisable hooks
Explanation: Theme, Auth, Locale separate contexts.
8

Passing object in Provider value={{user, setUser}} without memo:

ANew reference each render may re-render all consumers
BAlways optimal
CForbidden
DSSR only
Explanation: Memoize value object when needed.
9

Context is best for:

ALow-frequency global data like theme/auth
BEvery keystroke local input
CReplacing all state
DBinary files
Explanation: Not for high-frequency fine-grained updates.
10

Consumer component (legacy) vs useContext:

AuseContext is modern hook approach
BConsumer is only way in React 18
CNeither works in functions
DBoth removed
Explanation: Prefer useContext in function components.

10 Advanced React Context API MCQs

1

Splitting context by concern reduces:

AUnnecessary re-renders
BBundle size to zero
CNeed for components
DHTML
Explanation: AuthContext vs ThemeContext decouple updates.
2

useMemo on Provider value prevents:

ANew object identity every parent render
BAll context usage
CChildren mounting
DHooks
Explanation: const v = useMemo(() => ({...}), [deps]).
3

Context + useReducer pattern mimics:

ALightweight global store
BSQL database
CCSS modules
DRouter only
Explanation: dispatch and state via context.
4

Server Components note:

AClient context does not apply on server the same way
BContext banned everywhere
COnly classes
DNo React 18
Explanation: RSC has different data passing model.
5

Testing components using context:

AWrap render in Provider with test value
BSkip Provider
CUse only Enzyme
DCannot test
Explanation: RTL: render(<Provider><Ui /></Provider>).
6

Dynamic context value updates when:

AProvider value prop changes
BChild mutates context directly
Cnpm updates
DNever
Explanation: Children cannot mutate context—update via Provider.
7

Compound component + context often:

AShares internal state without exporting all props
BReplaces DOM
CUses jQuery
DDeletes children
Explanation: Tabs, Accordion patterns.
8

Context vs Redux:

ARedux adds devtools, middleware, structured updates; Context is built-in simpler
BIdentical always
CContext replaces Redux always
DRedux is built into React
Explanation: Choose based on complexity and tooling needs.
9

Forwarding refs separate from context:

AforwardRef for DOM ref; context for data
BSame API
CCannot combine
DForbidden
Explanation: Different problems.
10

Performance: context with rapidly changing value:

AMay re-render large subtree—consider state colocation or external store
BAlways fastest
CDisables memo
DOnly SSR issue
Explanation: Profile and narrow Provider scope.

15 React Context API Interview Questions & Answers

Easy Medium Hard
1What problem does Context solve?easy
Answer: Sharing data across components without passing props at every level.
2How to create and use context?easy
Answer: createContext, wrap Provider, read with useContext in descendants.
3What is prop drilling?medium
Answer: Passing props through many layers that do not need them.
4When NOT to use Context?medium
Answer: For frequently changing fine-grained state or as replacement for all local state.
5How to optimize Context performance?hard
Answer: Split contexts, memoize Provider value, colocate state.
6Default context value purpose?medium
Answer: Fallback for components rendered outside Provider (e.g. tests).
7Can context replace Redux?medium
Answer: For simple apps yes; complex apps may still need Redux/Zustand.
8useContext subscription behavior?medium
Answer: Component re-renders when context value it reads changes.
9Multiple providers nested?easy
Answer: Inner Provider overrides value for its subtree.
10Custom hook with context example?medium
Answer: function useAuth() { return useContext(AuthContext); }
11Context and TypeScript?hard
Answer: createContext<Type | null>(null) with null checks or default.
12Provider value as inline object issue?hard
Answer: New reference each render can re-render all consumers.
13Context vs composition?hard
Answer: Sometimes pass JSX children instead of context for flexibility.
14Testing with mock Provider?medium
Answer: Supply test doubles for auth user, theme, etc.
15Security: context for auth?hard
Answer: OK for client UX; never store secrets—validate on server.