← Back to React Course | Chapter 9: State Management | Lesson 1 of 10

State Management with Context API

Using the Context API for state management is like installing a house-wide intercom instead of shouting messages from room to room through every hallway in between.

The Problem: Prop Drilling

Without shared state management, passing a piece of data from a top-level component down to a deeply nested one means passing it as a prop through every component in between, even ones that don't use it themselves. This is called 'prop drilling', and it makes refactoring painful.

Note: If you're passing the same prop through three or more component layers unchanged, that's a strong signal to reach for Context.

Warning: Not every piece of state needs Context — for state used by only a parent and its direct child, plain props are simpler and fine.

Example: The Problem: Prop Drilling

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function GrandChild({ user }) { return <p>Hello, {user}</p>; }
function Child({ user }) { return <GrandChild user={user} />; }
function App() {
  const user = "Asha";
  return <Child user={user} />;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Solving It with Context

Context lets a top-level Provider make a value available to any descendant, no matter how deeply nested, without passing it through every layer as a prop. Any component that needs the value calls useContext directly, skipping the components in between entirely.

Note: createContext() and the Provider are usually defined once, near the top of your app, and imported wherever needed.

Warning: A component using useContext must be rendered INSIDE the matching Provider, or it gets the context's default value instead.

Example: Solving It with Context

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
const UserContext = React.createContext("Guest");
function GrandChild() {
  const user = React.useContext(UserContext);
  return <p>Hello, {user}</p>;
}
function Child() { return <GrandChild />; }
function App() {
  return <UserContext.Provider value="Asha"><Child /></UserContext.Provider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Combining Context with useState for Full State Management

Pairing Context with useState turns it into a lightweight but complete state-management solution: the Provider component holds the state and setter, and any descendant can both read the current value and update it, all without an external library.

Note: For state with more complex update logic, swap useState for useReducer inside the same Provider pattern — the Context part stays identical.

Warning: As an app grows large, a single Context holding many unrelated pieces of state can become as hard to manage as prop drilling was — split into focused contexts when this happens.

Example: Combining Context with useState for Full State Management

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
const CountContext = React.createContext();
function CountProvider({ children }) {
  const [count, setCount] = React.useState(0);
  return <CountContext.Provider value={{ count, setCount }}>{children}</CountContext.Provider>;
}
function Counter() {
  const { count, setCount } = React.useContext(CountContext);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
function App() {
  return <CountProvider><Counter /></CountProvider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Using one giant Context for all app state instead of splitting into focused contexts (auth, theme, cart).
  2. Forgetting that every consumer re-renders whenever the context value changes, even if they only use part of it.
  3. Not memoizing the Provider's value, causing unnecessary re-renders on every parent render.
Chapter Summary
  • Context API state management uses createContext + useState + a Provider to share state app-wide.
  • It avoids 'prop drilling' — manually passing state through many component layers that don't need it themselves.
  • For simple to medium apps, Context + useReducer/useState is often enough without a separate library like Redux.
  • Splitting state into multiple focused contexts limits how many components re-render on any given change.
Browser Support

Available since React 16.3 (stable Context API).

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.