dev.log / syntax diaries

Practical code notes, tools, and guided learning for developers.

Practical guides, developer tools, and tutorials for modern web developers, with the same focused tone across writing, utilities, and learning tracks.

BlogToolsTutorialsAboutContactAdmin Login
Privacy PolicyTerms of ServiceCookie Policy

ยฉ 2026 The Syntax Diaries ยท System_Operational

The Syntax Diaries logoThe Syntax Diaries
BlogToolsTutorialsAbout
build log live
Tutorial / React
Advanced Hooks30 minbeginner

useContext Hook

Learn how to share state between components globally using useContext, eliminating the need for prop drilling.

On This Page

The Problem: Prop Drilling HelluseContext to the Rescue!Step 1: Create a ContextStep 2: Provide the ContextStep 3: Use the ContextStep 4: Wrap Your AppLet's Build a Complete Theme SystemMultiple Contexts: Shopping Cart ExampleWhen to Use Context vs PropsUse Context When:Use Props When:Context Best PracticesPractice 1: Create Custom HooksPractice 2: Split Large ContextsPractice 3: Optimize Context UpdatesPractice Exercise: Build a Language SwitcherCommon Context MistakesMistake 1: Using Context for EverythingMistake 2: Not Providing Default ValuesMistake 3: Performance IssuesWhat We've LearnedQuick Recap QuizWhat's Next?

useContext Hook#

Imagine you're building a house, and every room needs electricity. You could run extension cords from room to room to room, creating a tangled mess. Or you could install a proper electrical system that makes power available everywhere it's needed.

That's exactly what useContext does for your React app! Instead of passing data through props from component to component (like extension cords), useContext creates a "power grid" that makes data available to any component that needs it.

The Problem: Prop Drilling Hell#

Let's start by understanding the problem useContext solves. Imagine you're building an app where many components need to know the current user's information:

function App() {
  const [user, setUser] = useState({ name: 'Alice', theme: 'dark' });
  
  return (
    <div>
      <Header user={user} />
      <MainContent user={user} />
      <Footer user={user} />
    </div>
  );
}

function Header({ user }) {
  return (
    <div>
      <Navigation user={user} />
      <UserMenu user={user} />
    </div>
  );
}

function Navigation({ user }) {
  return (
    <nav>
      <WelcomeMessage user={user} />
      <ThemeToggle user={user} />
    </nav>
  );
}

function WelcomeMessage({ user }) {
  return <span>Welcome, {user.name}!</span>; // Finally using it!
}

function ThemeToggle({ user }) {
  return (
    <button className={user.theme}>
      Switch to {user.theme === 'dark' ? 'light' : 'dark'} theme
    </button>
  );
}

See the problem? We're passing "user" through 4 levels of components just to get it to where it's actually needed! This is called "prop drilling" and it's a nightmare to maintain.

What happens when:

  • You add more components that need user data?
  • You want to add more user properties?
  • You need to update user data from a deeply nested component?

Your props become a tangled mess! ๐Ÿ

useContext to the Rescue!#

useContext lets you create a "global state" that any component can access directly, without prop drilling. Here's how it works:

Step 1: Create a Context#

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

// Create a context
const UserContext = createContext();

Step 2: Provide the Context#

function UserProvider({ children }) {
  const [user, setUser] = useState({ 
    name: 'Alice', 
    theme: 'dark',
    email: 'alice@example.com'
  });
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

Step 3: Use the Context#

function WelcomeMessage() {
  const { user } = useContext(UserContext);
  return <span>Welcome, {user.name}!</span>;
}

function ThemeToggle() {
  const { user, setUser } = useContext(UserContext);
  
  const toggleTheme = () => {
    setUser(prev => ({
      ...prev,
      theme: prev.theme === 'dark' ? 'light' : 'dark'
    }));
  };
  
  return (
    <button onClick={toggleTheme} className={user.theme}>
      Switch to {user.theme === 'dark' ? 'light' : 'dark'} theme
    </button>
  );
}

Step 4: Wrap Your App#

function App() {
  return (
    <UserProvider>
      <div>
        <Header />      {/* No props needed! */}
        <MainContent /> {/* No props needed! */}
        <Footer />      {/* No props needed! */}
      </div>
    </UserProvider>
  );
}

Amazing! No more prop drilling! Any component inside "UserProvider" can access user data directly.

Let's Build a Complete Theme System#

Let's create a real-world example - a theme system that lets users switch between light and dark modes:

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

// 1. Create the Theme Context
const ThemeContext = createContext();

// 2. Create a custom hook for easier usage
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

// 3. Create the Theme Provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const themeStyles = {
    light: {
      backgroundColor: '#ffffff',
      color: '#333333',
      border: '1px solid #dddddd'
    },
    dark: {
      backgroundColor: '#333333',
      color: '#ffffff',
      border: '1px solid #555555'
    }
  };
  
  return (
    <ThemeContext.Provider value={{
      theme,
      toggleTheme,
      styles: themeStyles[theme]
    }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 4. Components that use the theme
function Header() {
  const { styles } = useTheme();
  
  return (
    <header style={{
      ...styles,
      padding: '20px',
      textAlign: 'center',
      borderBottom: styles.border
    }}>
      <h1>My Awesome App</h1>
      <ThemeToggleButton />
    </header>
  );
}

function ThemeToggleButton() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <button 
      onClick={toggleTheme}
      style={{
        padding: '10px 20px',
        backgroundColor: theme === 'light' ? '#007bff' : '#ffc107',
        color: theme === 'light' ? 'white' : 'black',
        border: 'none',
        borderRadius: '5px',
        cursor: 'pointer'
      }}
    >
      Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
}

function MainContent() {
  const { styles, theme } = useTheme();
  
  return (
    <main style={{
      ...styles,
      padding: '40px',
      minHeight: '400px'
    }}>
      <h2>Welcome to the {theme} theme!</h2>
      <p>This content automatically adapts to the current theme.</p>
      
      <div style={{ marginTop: '20px' }}>
        <Card title="Example Card">
          <p>This card also uses the theme context!</p>
        </Card>
      </div>
    </main>
  );
}

function Card({ title, children }) {
  const { styles } = useTheme();
  
  return (
    <div style={{
      ...styles,
      padding: '20px',
      borderRadius: '8px',
      margin: '10px 0',
      boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
    }}>
      <h3 style={{ marginTop: 0 }}>{title}</h3>
      {children}
    </div>
  );
}

function Footer() {
  const { styles } = useTheme();
  
  return (
    <footer style={{
      ...styles,
      padding: '20px',
      textAlign: 'center',
      borderTop: styles.border
    }}>
      <p>&copy; 2024 My Awesome App. Built with React Context!</p>
    </footer>
  );
}

// 5. The main App component
function App() {
  return (
    <ThemeProvider>
      <div>
        <Header />
        <MainContent />
        <Footer />
      </div>
    </ThemeProvider>
  );
}

What makes this example great?

  • โœ… No prop drilling - theme data flows directly to components that need it
  • โœ… Custom hook - "useTheme()" provides a clean API
  • โœ… Error handling - throws error if used outside provider
  • โœ… Complete theming - styles automatically update everywhere
  • โœ… Easy to extend - adding new theme properties is simple

Multiple Contexts: Shopping Cart Example#

You can use multiple contexts in the same app. Let's add a shopping cart context to our theme example:

// Shopping Cart Context
const CartContext = createContext();

function useCart() {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error('useCart must be used within a CartProvider');
  }
  return context;
}

function CartProvider({ children }) {
  const [items, setItems] = useState([]);
  
  const addItem = (product) => {
    setItems(prev => {
      const existingItem = prev.find(item => item.id === product.id);
      if (existingItem) {
        return prev.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      return [...prev, { ...product, quantity: 1 }];
    });
  };
  
  const removeItem = (productId) => {
    setItems(prev => prev.filter(item => item.id !== productId));
  };
  
  const getTotalItems = () => {
    return items.reduce((total, item) => total + item.quantity, 0);
  };
  
  const getTotalPrice = () => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
  };
  
  return (
    <CartContext.Provider value={{
      items,
      addItem,
      removeItem,
      getTotalItems,
      getTotalPrice
    }}>
      {children}
    </CartContext.Provider>
  );
}

// Header with cart info
function HeaderWithCart() {
  const { styles } = useTheme();
  const { getTotalItems } = useCart();
  
  return (
    <header style={{ ...styles, padding: '20px', display: 'flex', justifyContent: 'space-between' }}>
      <h1>Shopping App</h1>
      
      <div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
        <CartIcon />
        <ThemeToggleButton />
      </div>
    </header>
  );
}

function CartIcon() {
  const { getTotalItems } = useCart();
  const totalItems = getTotalItems();
  
  return (
    <div style={{ position: 'relative' }}>
      <span style={{ fontSize: '24px' }}>๐Ÿ›’</span>
      {totalItems > 0 && (
        <span style={{
          position: 'absolute',
          top: '-5px',
          right: '-5px',
          backgroundColor: 'red',
          color: 'white',
          borderRadius: '50%',
          width: '20px',
          height: '20px',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: '12px'
        }}>
          {totalItems}
        </span>
      )}
    </div>
  );
}

function ProductList() {
  const { addItem } = useCart();
  const { styles } = useTheme();
  
  const products = [
    { id: 1, name: 'Cool T-Shirt', price: 19.99 },
    { id: 2, name: 'Nice Shoes', price: 89.99 },
    { id: 3, name: 'Awesome Hat', price: 24.99 }
  ];
  
  return (
    <div style={{ ...styles, padding: '20px' }}>
      <h2>Products</h2>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px' }}>
        {products.map(product => (
          <div key={product.id} style={{
            ...styles,
            padding: '15px',
            borderRadius: '8px',
            textAlign: 'center'
          }}>
            <h3>{product.name}</h3>
            <p>$" + product.price + "</p>
            <button
              onClick={() => addItem(product)}
              style={{
                padding: '8px 16px',
                backgroundColor: '#28a745',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer'
              }}
            >
              Add to Cart
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

// App with multiple providers
function MultiContextApp() {
  return (
    <ThemeProvider>
      <CartProvider>
        <div>
          <HeaderWithCart />
          <ProductList />
        </div>
      </CartProvider>
    </ThemeProvider>
  );
}

When to Use Context vs Props#

Use Context When:#

โœ… Many components need the same data (theme, user, language)
โœ… Data needs to be accessible deep in the component tree
โœ… You're tired of prop drilling
โœ… The data doesn't change very frequently

Use Props When:#

โœ… Data is only needed by direct children
โœ… You want to keep components reusable
โœ… The relationship between components is clear
โœ… Data changes frequently (might cause performance issues with context)

Context Best Practices#

Practice 1: Create Custom Hooks#

// โŒ Using context directly
function MyComponent() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('Must be used within UserProvider');
  }
  // ... rest of component
}

// โœ… Custom hook
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
}

function MyComponent() {
  const { user, setUser } = useUser(); // Much cleaner!
  // ... rest of component
}

Practice 2: Split Large Contexts#

// โŒ One massive context
const AppContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [cart, setCart] = useState([]);
  const [notifications, setNotifications] = useState([]);
  // ... and 20 more pieces of state
}

// โœ… Separate concerns
function UserProvider({ children }) { /* user logic */ }
function ThemeProvider({ children }) { /* theme logic */ }
function CartProvider({ children }) { /* cart logic */ }
function NotificationProvider({ children }) { /* notification logic */ }

Practice 3: Optimize Context Updates#

// โŒ Single context with mixed concerns
const AppContext = createContext();

function AppProvider({ children }) {
  const [fastChangingData, setFastChangingData] = useState(0);
  const [slowChangingData, setSlowChangingData] = useState('stable');
  
  return (
    <AppContext.Provider value={{
      fastChangingData, setFastChangingData,
      slowChangingData, setSlowChangingData
    }}>
      {children}
    </AppContext.Provider>
  );
}

// โœ… Split by update frequency
const FastDataContext = createContext();
const SlowDataContext = createContext();

function FastDataProvider({ children }) {
  const [data, setData] = useState(0);
  return (
    <FastDataContext.Provider value={{ data, setData }}>
      {children}
    </FastDataContext.Provider>
  );
}

function SlowDataProvider({ children }) {
  const [data, setData] = useState('stable');
  return (
    <SlowDataContext.Provider value={{ data, setData }}>
      {children}
    </SlowDataContext.Provider>
  );
}

Practice Exercise: Build a Language Switcher#

Try building a multi-language app with useContext:

Requirements:

  1. Support English and Spanish languages
  2. Allow switching between languages
  3. Have translations for common phrases
  4. Store current language in localStorage
  5. Any component should be able to access translations

Starter code:

const translations = {
  en: {
    welcome: 'Welcome',
    hello: 'Hello',
    goodbye: 'Goodbye',
    language: 'Language',
    switchTo: 'Switch to'
  },
  es: {
    welcome: 'Bienvenido',
    hello: 'Hola',
    goodbye: 'Adiรณs',
    language: 'Idioma',
    switchTo: 'Cambiar a'
  }
};

// Your context code here!

function LanguageApp() {
  return (
    // Your provider here
    <div>
      <Header />
      <MainContent />
      <LanguageSwitcher />
    </div>
    // Close provider
  );
}

Common Context Mistakes#

Mistake 1: Using Context for Everything#

// โŒ Don't put everything in context
const MegaContext = createContext();

function MegaProvider({ children }) {
  const [count, setCount] = useState(0); // Only used in one component
  const [tempData, setTempData] = useState(''); // Changes frequently
  const [theme, setTheme] = useState('light'); // Good candidate for context
  
  return (
    <MegaContext.Provider value={{ /* everything */ }}>
      {children}
    </MegaContext.Provider>
  );
}

// โœ… Only put truly global state in context
const ThemeContext = createContext();
// Use regular state for component-specific data

Mistake 2: Not Providing Default Values#

// โŒ No default value
const UserContext = createContext();

// โœ… Provide sensible defaults
const UserContext = createContext({
  user: null,
  setUser: () => {},
  loading: true
});

Mistake 3: Performance Issues#

// โŒ Creating new objects every render
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  
  return (
    <UserContext.Provider value={{
      user,
      setUser,
      helpers: { // New object every render!
        isLoggedIn: !!user,
        userName: user?.name || 'Guest'
      }
    }}>
      {children}
    </UserContext.Provider>
  );
}

// โœ… Memoize expensive calculations
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  
  const contextValue = useMemo(() => ({
    user,
    setUser,
    helpers: {
      isLoggedIn: !!user,
      userName: user?.name || 'Guest'
    }
  }), [user]);
  
  return (
    <UserContext.Provider value={contextValue}>
      {children}
    </UserContext.Provider>
  );
}

What We've Learned#

Congratulations! You now understand:

โœ… What prop drilling is and why it's problematic
โœ… How to create and use React Context
โœ… When to use Context vs regular props
โœ… How to build custom hooks for Context
โœ… Best practices for organizing multiple contexts
โœ… How to avoid common Context pitfalls

Quick Recap Quiz#

Test your Context knowledge:

  1. What problem does useContext solve?
  2. What are the three steps to use Context in React?
  3. When should you NOT use Context?
  4. Why is it recommended to create custom hooks for Context?

Answers: 1) Eliminates prop drilling, 2) Create context, provide it, consume it, 3) For frequently changing data or simple parent-child communication, 4) Better error handling and cleaner API

What's Next?#

In our next lesson, we'll learn about useReducer - React's powerful hook for managing complex state logic. When useState becomes too simple and you need more sophisticated state management, useReducer is your next step up!

useContext handles sharing state, and useReducer handles complex state logic!

Previous

Custom Hooks

Next

useEffect Hook