Context API
Overview
To solve the Prop Drilling problem without requiring third-party libraries, React provides a built-in feature called the Context API.
Context acts like a global teleportation wormhole. You wrap a high-level component (like <App />) in a Context Provider and feed it some data (like the user's theme or login status). Once provided, that data is instantly 'broadcasted' to the entire component tree below it.
Any component inside that tree, whether it is 1 level deep or 50 levels deep, can 'tap into' the wormhole and consume the data directly, completely bypassing all the components in between. When the Provider's data changes, React automatically re-renders every component that is actively consuming that specific context.
Syntax
import { createContext, useState } from 'react';
// 1. Create the Context (usually exported so others can import it)
export const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('dark');
return (
// 2. Wrap the application tree in the Provider.
// The 'value' prop is the data you are broadcasting to the universe.
<ThemeContext.Provider value={theme}>
<div className="app-layout">
{/* We NO LONGER pass theme as a prop! */}
<Header />
<MainContent />
</div>
</ThemeContext.Provider>
);
}Common Pitfalls
- The Object Value Re-render Trap: If you pass an object inline into a provider:
<Provider value={{ user, setUser }}>, React creates a brand new object in memory every single time the Provider re-renders. Because the memory reference changes, React forces EVERY component consuming the context to re-render, destroying performance. You must wrap objects inuseMemobefore passing them tovalue.
Interview Questions
createContext(defaultValue)?The default value is ONLY used if a component attempts to consume the context but there is NO matching <Provider> anywhere above it in the component tree. It is mostly used for testing components in isolation without having to wrap them in mock Providers.
The Context API is not optimized for high-frequency updates. Whenever the Provider's value changes, React traverses the tree to re-render every consumer. For rapidly changing data, specialized state managers like Zustand or Redux (which use selectors to prevent unnecessary renders) are required.
Real-World Example
Optimized Authentication Provider: In enterprise apps, you rarely expose the raw Provider in your App.jsx. You create an AuthProvider wrapper component that encapsulates the state logic, the memoization, and the provider itself, keeping App.jsx incredibly clean.
import { createContext, useMemo, useState } from 'react';
export const AuthContext = createContext(null);
// Best Practice: Abstract the Provider into its own component
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
// Login function
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
// CRITICAL: We memoize the object so its memory address doesn't
// change on every render, saving our app from performance death!
const contextValue = useMemo(() => {
return { user, login, logout };
}, [user]);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
}Check Your Knowledge
Test your understanding of Context API with these quick questions.