Module 4: State Management

Learn state management in React including Context API, Redux, Zustand, and other state management solutions.

Back to Course|5 hours|Advanced

State Management

Learn state management in React including Context API, Redux, Zustand, and other state management solutions.

Progress: 0/4 topics completed0%

Select Topics Overview

Context API

Learn React Context API for sharing state across components without prop drilling

Content by: Ayush Ladani

React.js Developer

Connect

What is Context API?

Context API is a React feature that allows you to share state across components without having to explicitly pass props through every level of the component tree.

When to Use Context

  • Sharing global state across many components
  • Avoiding prop drilling
  • Theme or language preferences
  • User authentication state
  • Application-wide settings

Basic Context Setup

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

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

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

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

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

// Custom hook to use the context
function useTheme() {
    const context = useContext(ThemeContext);
    if (!context) {
        throw new Error('useTheme must be used within a ThemeProvider');
    }
    return context;
}

// Using the context in components
function App() {
    return (
        <ThemeProvider>
            <Header />
            <Main />
        </ThemeProvider>
    );
}

function Header() {
    const { theme, toggleTheme } = useTheme();
    
    return (
        <header className={theme}>
            <h1>My App</h1>
            <button onClick={toggleTheme}>
                Switch to {theme === 'light' ? 'Dark' : 'Light'}
            </button>
        </header>
    );
}

// Advanced Context with multiple values
const UserContext = createContext();

function UserProvider({ children }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(false);

    const login = async (credentials) => {
        setLoading(true);
        try {
            // Simulate API call
            const response = await fetch('/api/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(credentials)
            });
            const userData = await response.json();
            setUser(userData);
        } catch (error) {
            throw error;
        } finally {
            setLoading(false);
        }
    };

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

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

function useUser() {
    const context = useContext(UserContext);
    if (!context) {
        throw new Error('useUser must be used within a UserProvider');
    }
    return context;
}

// Using multiple contexts
function App() {
    return (
        <ThemeProvider>
            <UserProvider>
                <Header />
                <Main />
            </UserProvider>
        </ThemeProvider>
    );
}

function Header() {
    const { theme, toggleTheme } = useTheme();
    const { user, logout } = useUser();
    
    return (
        <header className={theme}>
            <h1>My App</h1>
            <button onClick={toggleTheme}>
                Switch to {theme === 'light' ? 'Dark' : 'Light'}
            </button>
            {user && (
                <div>
                    <span>Welcome, {user.name}</span>
                    <button onClick={logout}>Logout</button>
                </div>
            )}
        </header>
    );
}
Swipe to see more code

Practice Exercise: Context API E-commerce

Code Example
// Exercise: Build an E-commerce App with Context API
// Create a shopping cart system using Context API

// src/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
                    )
                };
            } else {
                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;
    }
};

function CartProvider({ children }) {
    const [state, dispatch] = useReducer(cartReducer, {
        items: [],
        total: 0
    });

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

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

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

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

    const getTotal = () => {
        return state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    };

    const getItemCount = () => {
        return state.items.reduce((count, item) => count + item.quantity, 0);
    };

    return (
        <CartContext.Provider value={{
            items: state.items,
            addItem,
            removeItem,
            updateQuantity,
            clearCart,
            getTotal,
            getItemCount
        }}>
            {children}
        </CartContext.Provider>
    );
}

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

// src/components/ProductCard.js
function ProductCard({ product }) {
    const { addItem } = useCart();

    return (
        <div className="product-card">
            <img src={product.image} alt={product.name} />
            <h3>{product.name}</h3>
            <p>{product.price}</p>
            <button onClick={() => addItem(product)}>
                Add to Cart
            </button>
        </div>
    );
}

// src/components/Cart.js
function Cart() {
    const { items, removeItem, updateQuantity, getTotal, clearCart } = useCart();

    if (items.length === 0) {
        return <div>Your cart is empty</div>;
    }

    return (
        <div className="cart">
            <h2>Shopping Cart</h2>
            {items.map(item => (
                <div key={item.id} className="cart-item">
                    <img src={item.image} alt={item.name} />
                    <div>
                        <h3>{item.name}</h3>
                        <p>{item.price}</p>
                        <input
                            type="number"
                            value={item.quantity}
                            onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
                            min="1"
                        />
                        <button onClick={() => removeItem(item.id)}>
                            Remove
                        </button>
                    </div>
                </div>
            ))}
            <div className="cart-total">
                <strong>Total: {getTotal()}</strong>
            </div>
            <button onClick={clearCart}>Clear Cart</button>
        </div>
    );
}

// src/components/CartIcon.js
function CartIcon() {
    const { getItemCount } = useCart();
    const itemCount = getItemCount();

    return (
        <div className="cart-icon">
            🛒
            {itemCount > 0 && (
                <span className="cart-count">{itemCount}</span>
            )}
        </div>
    );
}

// src/App.js
function App() {
    return (
        <CartProvider>
            <div className="app">
                <header>
                    <h1>E-commerce Store</h1>
                    <CartIcon />
                </header>
                <main>
                    <ProductList />
                    <Cart />
                </main>
            </div>
        </CartProvider>
    );
}

// Challenge: Add a wishlist feature
// Challenge: Add product categories and filtering
// Challenge: Add user authentication context
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Additional Resources

📚 Recommended Reading

  • Redux Toolkit Documentation
  • Zustand Documentation
  • React State Management Patterns

🌐 Online Resources

  • Context API Tutorial
  • Redux Toolkit Guide
  • Zustand Examples

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 5