Mastering React Context API: Eliminate Prop Drilling with Clean Code Examples

4 min read

Mastering React Context API: Eliminate Prop Drilling with Clean Code Examples

A practical, code-heavy guide to using React’s Context API for efficient global state management without external libraries.

1. Introduction

React’s Context API is the built-in solution for sharing state across components without prop drilling. Ideal for themes, authentication, language settings, or any global data.

No extra dependencies. Lightweight. Perfect for most medium-sized apps.

Let’s skip the theory and jump straight into code with multiple practical examples.

2. The Problem: Prop Drilling

Without Context, global data forces you to pass props through every level:

// Bad: Prop drilling hell
<App>
  <Header user={user} />
  <Main>
    <Sidebar user={user} />
    <Content>
      <DeepComponent user={user} />  {/* user passed through 5 levels */}
    </Content>
  </Main>
</App>

Clean code becomes messy fast.

3. The Solution: Context API

Create once, provide at the top, consume anywhere.

Core tools:
createContext()
Provider
useContext() hook

4. Implementation

Example 1: Theme Toggle (Most Common Use Case)

ThemeContext.js

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export const useTheme = () => useContext(ThemeContext);

App.js

import { ThemeProvider } from './ThemeContext';
import Header from './Header';
import Dashboard from './Dashboard';

function App() {
  return (
    <ThemeProvider>
      <div className="app">
        <Header />
        <Dashboard />
      </div>
    </ThemeProvider>
  );
}

export default App;

Header.js (uses context directly)

import { useTheme } from './ThemeContext';

function Header() {
  const { theme, toggleTheme } = useTheme();

  return (
    <header className={`header ${theme}`}>
      <h1>My App</h1>
      <button onClick={toggleTheme}>
        {theme === 'light' ? 'Dark' : 'Light'}
      </button>
    </header>
  );
}

export default Header;

DeepComponent.js (deeply nested – still easy access)

import { useTheme } from './ThemeContext';

function SettingsPanel() {
  const { theme } = useTheme();

  return (
    <div className={`panel ${theme}`}>
      <p>Current theme: {theme}</p>
      {/* No props needed! */}
    </div>
  );
}

export default SettingsPanel;

Example 2: User Authentication Context

AuthContext.js

import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userData) => {
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  const isAuthenticated = !!user;

  return (
    <AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

LoginButton.js

import { useAuth } from './AuthContext';

function LoginButton() {
  const { user, login, logout } = useAuth();

  if (user) {
    return <button onClick={logout}>Logout {user.name}</button>;
  }

  return <button onClick={() => login({ name: 'John' })}>Login</button>;
}

export default LoginButton;

ProtectedComponent.js (deep in the tree)

import { useAuth } from './AuthContext';

function Dashboard() {
  const { isAuthenticated, user } = useAuth();

  if (!isAuthenticated) return <p>Please log in</p>;

  return <h2>Welcome back, {user.name}!</h2>;
}

export default Dashboard;

Example 3: Advanced – Context + useReducer (Redux-like)

CartContext.js

import { createContext, useContext, useReducer } from 'react';

const CartContext = createContext();

const initialState = { items: [], total: 0 };

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return {
        items: [...state.items, action.payload],
        total: state.total + action.payload.price
      };
    case 'REMOVE_ITEM':
      const filtered = state.items.filter(item => item.id !== action.payload);
      const removed = state.items.find(item => item.id === action.payload);
      return {
        items: filtered,
        total: state.total - (removed?.price || 0)
      };
    case 'CLEAR':
      return initialState;
    default:
      return state;
  }
}

export function CartProvider({ children }) {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  return (
    <CartContext.Provider value={{ state, dispatch }}>
      {children}
    </CartContext.Provider>
  );
}

export const useCart = () => useContext(CartContext);

AddToCartButton.js

import { useCart } from './CartContext';

function AddToCartButton({ product }) {
  const { dispatch } = useCart();

  return (
    <button onClick={() => dispatch({ type: 'ADD_ITEM', payload: product })}>
      Add to Cart
    </button>
  );
}

export default AddToCartButton;

CartSummary.js (anywhere in app)

import { useCart } from './CartContext';

function CartSummary() {
  const { state } = useCart();

  return (
    <div>
      <p>Items: {state.items.length}</p>
      <p>Total: ${state.total}</p>
    </div>
  );
}

export default CartSummary;

Bonus: Multiple Contexts (Best Practice)

// App.js – Multiple providers
<ThemeProvider>
  <AuthProvider>
    <CartProvider>
      <AppContent />
    </CartProvider>
  </AuthProvider>
</ThemeProvider>

5. Conclusion

Context API eliminates prop drilling with minimal code.

Use it for:
• Themes
• Auth state
• User preferences
• Shopping cart
• Language settings

Combine with useReducer for complex logic.

Custom hooks (useTheme, useAuth) make consumption clean.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

Your email address will not be published. Required fields are marked *