React Context API

Sharing data across component tree without props

🌐 What is Context API?

Context API provides a way to share values between components without passing props through every level. It's perfect for global data like themes, user authentication, or language preferences.


import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}
                                    

Context API Concepts

🏗️

Create Context

Initialize a new context object

const MyContext = createContext(defaultValue);
📤

Provider

Supplies context value to children

<MyContext.Provider value={data}>
📥

Consumer

Accesses context value in components

const value = useContext(MyContext);
🔗

No Prop Drilling

Skip intermediate components

// Direct access anywhere in tree

🔹 Basic Context Example

Create a simple theme context to share theme data across components. This eliminates the need to pass theme props through every component level.

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

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

// 2. Create Provider Component
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>
  );
}

// 3. Create Custom Hook for Easy Access
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// 4. Use in Components
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff'
      }}
    >
      Current theme: {theme}
    </button>
  );
}

// 5. Wrap App with Provider
function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

Output:

🔹 User Authentication Context

A practical example managing user authentication state. This context provides login, logout functionality and user data throughout your application without prop drilling.

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

const AuthContext = createContext();

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

  const login = (username, password) => {
    // Simulate API call
    setUser({ username, id: 1 });
  };

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

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

function useAuth() {
  return useContext(AuthContext);
}

// Using in components
function LoginButton() {
  const { user, login, logout } = useAuth();

  if (user) {
    return (
      <div>
        <p>Welcome, {user.username}!</p>
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return (
    <button onClick={() => login('john', 'password123')}>
      Login
    </button>
  );
}

function App() {
  return (
    <AuthProvider>
      <LoginButton />
    </AuthProvider>
  );
}

Output (Before Login):

Output (After Login):

Welcome, john!

🔹 Multiple Contexts

You can use multiple contexts in your application. Nest providers to combine different global states like theme, authentication, and language settings.

import { createContext, useContext } from 'react';

const ThemeContext = createContext();
const LanguageContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <LanguageContext.Provider value="en">
        <Dashboard />
      </LanguageContext.Provider>
    </ThemeContext.Provider>
  );
}

function Dashboard() {
  const theme = useContext(ThemeContext);
  const language = useContext(LanguageContext);

  return (
    <div>
      <p>Theme: {theme}</p>
      <p>Language: {language}</p>
    </div>
  );
}

🔹 Context with useReducer

Combine Context API with useReducer for complex state management. This pattern is great for managing application-wide state with multiple actions.

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

const CartContext = createContext();

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return [...state, action.item];
    case 'REMOVE_ITEM':
      return state.filter(item => item.id !== action.id);
    case 'CLEAR_CART':
      return [];
    default:
      return state;
  }
}

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

  const addItem = (item) => dispatch({ type: 'ADD_ITEM', item });
  const removeItem = (id) => dispatch({ type: 'REMOVE_ITEM', id });
  const clearCart = () => dispatch({ type: 'CLEAR_CART' });

  return (
    <CartContext.Provider value={{ cart, addItem, removeItem, clearCart }}>
      {children}
    </CartContext.Provider>
  );
}

function useCart() {
  return useContext(CartContext);
}

function ShoppingCart() {
  const { cart, addItem, removeItem } = useCart();

  return (
    <div>
      <h2>Cart ({cart.length} items)</h2>
      <button onClick={() => addItem({ id: 1, name: 'Product' })}>
        Add Item
      </button>
      {cart.map(item => (
        <div key={item.id}>
          {item.name}
          <button onClick={() => removeItem(item.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

🔹 When to Use Context API

Context is powerful but should be used appropriately:

✅ Use Context when:

  • Global data: Theme, authentication, language settings
  • Prop drilling: Passing props through many levels
  • Shared state: Multiple components need same data
  • Configuration: App-wide settings and preferences

❌ Don't use Context when:

  • Frequent updates: Can cause performance issues
  • Local state: Data only needed in one component
  • Simple prop passing: Only one or two levels deep
  • Complex state logic: Consider state management libraries

🔹 Context Best Practices

Follow these patterns for effective Context usage:

// ✅ Good: Separate context file
// contexts/ThemeContext.js
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// ✅ Good: Split contexts by concern
// Don't put everything in one context

// ✅ Good: Memoize context value for performance
const value = useMemo(() => ({ theme, setTheme }), [theme]);

// ❌ Bad: Creating new object every render
<Context.Provider value={{ theme, setTheme }}>

🧠 Test Your Knowledge

What problem does Context API solve?