Learn how to use the Context API and useContext to share data across your React application, effectively eliminating prop drilling for cleaner code.
Previously in this course, we explored managing complex object-based state using useReducer. While that handles state transitions beautifully, we still face the challenge of getting that state into the components that actually need it.
In larger applications, passing data through every level of your component tree—a pattern known as prop drilling—becomes a maintenance nightmare. Today, we’ll solve this using the context API. This tool allows you to broadcast data to your component tree, letting any component "subscribe" to it directly, regardless of its depth.
Imagine your dashboard project. You have a User object that needs to be accessed by the Navbar, the ProfileSettings page, and the Sidebar. If you store this in your top-level App component, you have to pass that user prop through layers of intermediate components that don't even use the data.
This creates "middleman" components that are harder to test and reuse, as they are burdened with props they don't care about. The context API provides a way to share these values without explicit passing at every level.
To use context, we follow a three-step pattern:
Provider to set the value.useContext hook to access the data.First, create a new file, UserContext.js. We use createContext to initialize our storage.
JAVASCRIPTimport { createContext } from CE9178">'react'; // You can provide a default value here, usually null or an empty object export const UserContext = createContext(null);
The Provider is a component that accepts a value prop. Any component inside this provider can access that value. In our dashboard, we’ll wrap our main layout.
JAVASCRIPTimport { UserContext } from CE9178">'./UserContext'; function App() { const user = { name: CE9178">'Alex', role: CE9178">'Admin' }; return ( <UserContext.Provider value={user}> <Dashboard /> </UserContext.Provider> ); }
Now, any child component can grab the user data without props.
JAVASCRIPTimport { useContext } from CE9178">'react'; import { UserContext } from CE9178">'./UserContext'; function Navbar() { const user = useContext(UserContext); return <nav>Welcome, {user.name}</nav>; }
In our running dashboard project, let's inject the current user's theme preference.
ThemeContext.js file using createContext('light').App.js, wrap your main dashboard component with <ThemeContext.Provider value="dark">.ThemeButton component that uses useContext(ThemeContext) to display the current theme string.ThemeButton deep inside your dashboard hierarchy (e.g., inside a SettingsPanel inside Dashboard) and verify it receives the value without passing it as a prop.value passed to a Provider changes, all components consuming that context will re-render. We will cover how to optimize this in later lessons, but for now, keep your context values stable.Provider, you will get the default value you passed to createContext. Always ensure your Provider is high enough in the tree.The context API is your primary tool for avoiding prop drilling in React. By using createContext, the Provider component, and the useContext hook, you can build a more decoupled component architecture. While it’s powerful for global state, use it judiciously to keep your application performant and easy to debug.
Up next: We’ll look at Architecting Global State with Context and Reducer to create a robust, centralized store for our dashboard.
Master authentication state management by integrating useReducer. Learn to handle loading, errors, and token logic for a secure, predictable React flow.
Read moreStop React performance bottlenecks caused by the Context API. Learn how to split contexts, memoize values, and prevent unnecessary re-renders in your app.
Introduction to Context API
Dynamic Routing with URL Parameters
Nested Routes and Layouts
Protected Routes for Authenticated Views
Programmatic Navigation
Building the Dashboard Navigation Structure
Asynchronous Data Lifecycle
Caching Strategies with React Query
Mutations and Data Updates
Synchronizing Client and Server State
Integrating Live Data into the Dashboard
Error Handling and Loading UI
Controlled vs Uncontrolled Components
Real-time Form Validation
Schema-based Validation with Zod
Handling Multi-step Forms
Optimizing Form Submissions
Performance Profiling with React DevTools
Refactoring for Scalability
Finalizing Dashboard Data Flow
Deploying the Application
Advanced Hook Composition
Implementing Middleware for State
Advanced Context Patterns
Router Loaders and Data Prefetching
Complex Route Guards
Handling Large Datasets in UI
Testing Hooks and Components
Managing Global Modals
Implementing Keyboard Shortcuts
Optimizing Asset Loading
Internationalization Basics
Managing WebSocket Connections