React Intermediate

Context API and Global State Management

CodingerWeb
CodingerWeb
20 views 50 min read

Understanding React Context API

The Context API provides a way to pass data through the component tree without having to pass props down manually at every level.

When to Use Context

  • Theme data (light/dark mode)
  • User authentication state
  • Language/locale preferences
  • Shopping cart data

Creating a Context

// contexts/ThemeContext.js
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

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

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

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

    const value = {
        theme,
        toggleTheme
    };

    return (
        
            {children}
        
    );
}

Using Context in Components

// App.js
import React from 'react';
import { ThemeProvider } from './contexts/ThemeContext';
import Header from './components/Header';
import MainContent from './components/MainContent';

function App() {
    return (
        
            
); }
// components/Header.js
import React from 'react';
import { useTheme } from '../contexts/ThemeContext';

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

    return (
        

My App

); }

Complex State Management with useReducer

// contexts/CartContext.js
import React, { createContext, useContext, useReducer } from 'react';

const CartContext = createContext();

const cartReducer = (state, action) => {
    switch (action.type) {
        case 'ADD_ITEM':
            const existingItem = state.items.find(item => item.id === action.payload.id);
            if (existingItem) {
                return {
                    ...state,
                    items: state.items.map(item =>
                        item.id === action.payload.id
                            ? { ...item, quantity: item.quantity + 1 }
                            : item
                    )
                };
            }
            return {
                ...state,
                items: [...state.items, { ...action.payload, quantity: 1 }]
            };
        
        case 'REMOVE_ITEM':
            return {
                ...state,
                items: state.items.filter(item => item.id !== action.payload)
            };
        
        case 'UPDATE_QUANTITY':
            return {
                ...state,
                items: state.items.map(item =>
                    item.id === action.payload.id
                        ? { ...item, quantity: action.payload.quantity }
                        : item
                )
            };
        
        case 'CLEAR_CART':
            return {
                ...state,
                items: []
            };
        
        default:
            return state;
    }
};

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

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

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

    const addItem = (item) => {
        dispatch({ type: 'ADD_ITEM', payload: item });
    };

    const removeItem = (id) => {
        dispatch({ type: 'REMOVE_ITEM', payload: id });
    };

    const updateQuantity = (id, quantity) => {
        dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
    };

    const clearCart = () => {
        dispatch({ type: 'CLEAR_CART' });
    };

    const total = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);

    const value = {
        items: state.items,
        total,
        addItem,
        removeItem,
        updateQuantity,
        clearCart
    };

    return (
        
            {children}
        
    );
}

Multiple Contexts

// App.js with multiple providers
function App() {
    return (
        
            
                
                    
                        
                            } />
                            } />
                        
                    
                
            
        
    );
}

Practical Exercise

Create a user authentication context that manages:

  • User login/logout state
  • User profile information
  • Authentication tokens
  • Loading states

Best Practices

  • Don't overuse Context - not every state needs to be global
  • Split contexts by concern (theme, auth, cart, etc.)
  • Use custom hooks to consume context
  • Consider performance implications of context updates