What is Context and When Should You Use It?

As the MERN Blog grows, certain data needs to be accessible in many components at many different levels of the component tree โ€” the currently logged-in user, the active theme, a notification count. Passing this data as props through every intermediate component is called prop drilling, and it quickly makes the codebase tedious and fragile. React Context is the built-in solution: it lets you make data available to any component in the tree without threading it through props. Understanding when Context is the right tool โ€” and when it is overkill โ€” is the key decision this lesson addresses.

The Prop Drilling Problem

// Without Context: user passed as prop through every intermediate component
function App() {
  const [user, setUser] = useState(null);
  return <PageLayout user={user} setUser={setUser} />;
}

function PageLayout({ user, setUser }) {
  return (
    <div>
      <Header user={user} setUser={setUser} /> {/* passes down */}
      <main><Outlet /></main>
    </div>
  );
}

function Header({ user, setUser }) {
  return <nav><UserMenu user={user} setUser={setUser} /></nav> {/* passes down */}
}

function UserMenu({ user, setUser }) {
  // Only this component actually uses user โ€” all the above were just passing it
  return user
    ? <span>{user.name} <button onClick={() => setUser(null)}>Log Out</button></span>
    : <Link to="/login">Log In</Link>;
}
// Problem: PageLayout and Header know about user/setUser but never use it
// Adding a new prop means updating every component in the chain
Note: Context solves prop drilling โ€” but it is not a replacement for all state management. Context should hold data that is truly global to a significant portion of the application โ€” the authenticated user, the current theme, a locale setting. Data that is only used by a few nearby components should still be lifted state (Chapter 16 Lesson 4). Data that changes frequently and is needed widely (a shopping cart with item-level updates) benefits from a dedicated library like Zustand.
Tip: A useful way to decide: if you find yourself passing a prop through more than two components that do not themselves use it, it is a candidate for Context. If only two or three components need the data and they are relatively close in the tree, lifting state is simpler and more explicit. Context adds indirection โ€” it is worth adding only when the benefit of eliminating prop drilling outweighs the cost of making data flow implicit.
Warning: Every component that consumes a context re-renders whenever the context value changes โ€” regardless of whether the specific part of the value it uses actually changed. Putting too much into a single context causes unnecessary re-renders across the entire component tree. Keep contexts focused on a single concern (auth, theme, notifications) and split them rather than merging everything into one global store context.

Context vs Props vs Lifting State โ€” Decision Guide

Scenario Best Approach
Two sibling components share state Lift state to common parent, pass as props
State passes through 3+ components that do not use it React Context
Current logged-in user โ€” needed everywhere React Context (AuthContext)
Active theme โ€” affects most components React Context (ThemeContext)
A form’s field values โ€” local to the form Local useState inside the form
Large, complex global state with many actions Zustand, Redux Toolkit, or Jotai
Server state (API data, caching, background refetch) TanStack Query (React Query)

How Context Works โ€” Mental Model

Without Context:
  App โ†’ PageLayout โ†’ Header โ†’ UserMenu
  data flows through props at every step โ€” all components know about user

With Context:
  AuthProvider wraps the tree
       โ”‚
       โ”œโ”€โ”€ PageLayout (does not touch user โ€” transparent)
       โ”‚       โ”œโ”€โ”€ Header (does not touch user โ€” transparent)
       โ”‚       โ”‚       โ””โ”€โ”€ UserMenu โ† useAuth() reads user directly
       โ”‚       โ””โ”€โ”€ PostDetailPage โ† useAuth() reads user directly
       โ””โ”€โ”€ DashboardPage โ† useAuth() reads user directly

  Components that need user read it directly from context
  Components that don't need user are completely unaware of it

What Belongs in the MERN Blog Contexts

Context What It Holds Who Consumes It
AuthContext user, token, login(), logout(), register(), loading Header, ProtectedRoute, PostCard (show edit if owner), ProfilePage
ThemeContext theme (‘light’/’dark’), toggleTheme() Header (toggle button), any component with theme-dependent styling
NotificationContext notifications[], addNotification(), dismissNotification() NotificationBanner, any component that triggers notifications

Common Mistakes

Mistake 1 โ€” Using Context for data that only two components share

โŒ Wrong โ€” creating a context for a search query shared between SearchBar and PostList:

// SearchContext.jsx โ€” overkill for two adjacent siblings
// Just lift query to BlogPage and pass as props โœ“

โœ… Correct โ€” lift state to the nearest common ancestor for nearby components.

Mistake 2 โ€” Putting fast-changing data in a broad context

โŒ Wrong โ€” putting the post list in AuthContext alongside user data:

// Every time a post is liked, ALL context consumers re-render (including Header!)
const AuthContext = createContext({ user, posts, likedPostIds, ... });

โœ… Correct โ€” keep AuthContext focused on auth state only; post data belongs in page-level state.

Mistake 3 โ€” Reading context outside a Provider

โŒ Wrong โ€” using useAuth() in a component rendered above AuthProvider:

// App.jsx โ€” AuthProvider is NOT a parent of this component
function RootHeader() {
  const { user } = useAuth(); // undefined โ€” no Provider above this component
  return <header>{user?.name}</header>;
}
// Used above <AuthProvider> in the tree

โœ… Correct โ€” every component that consumes a context must be a descendant of its Provider.

Quick Reference

Concept Key Point
Context purpose Eliminate prop drilling for truly global data
When to use Prop passes through 3+ components that do not use it
When NOT to use Two nearby components sharing state โ€” lift instead
Re-render scope All consumers re-render when context value changes
Alternative for complex state Zustand, Redux Toolkit
Alternative for server state TanStack Query

🧠 Test Yourself

A SearchBar and a PostList on the same page need to share a search query string. The BlogPage is their direct parent. Should you use Context or lift state?