State management with Context API and Redux patterns

Alex Chang Feb 2026
2 tabs
import React, { createContext, useContext, useReducer, useState } from 'react';

// 1. Basic Context
const UserContext = createContext(null);

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

  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

  const value = {
    user,
    login,
    logout,
    isAuthenticated: !!user,
  };

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

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

// Usage
function LoginButton() {
  const { login, logout, isAuthenticated, user } = useUser();

  return isAuthenticated ? (
    <div>
      <span>Welcome, {user.name}</span>
      <button onClick={logout}>Logout</button>
    </div>
  ) : (
    <button onClick={() => login({ id: 1, name: 'Alex' })}>
      Login
    </button>
  );
}

// 2. Context with useReducer
const CartContext = createContext(null);

const cartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      const existing = state.items.find(item => item.id === action.payload.id);
      if (existing) {
        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;
  }
};

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

  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,
    addItem,
    removeItem,
    updateQuantity,
    clearCart,
    total,
    itemCount: state.items.reduce((sum, item) => sum + item.quantity, 0),
  };

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

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

// 3. Multiple contexts composed
function App() {
  return (
    <UserProvider>
      <CartProvider>
        <ThemeProvider>
          <MainApp />
        </ThemeProvider>
      </CartProvider>
    </UserProvider>
  );
}

// 4. Context with TypeScript-style interface
/*
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}
*/

const ThemeContext = createContext(null);

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>
  );
}
2 files · javascript Explain with highlit

State management solutions handle data flow in complex applications. I use React Context API for moderate state sharing without prop drilling. The createContext function creates context objects, while Provider passes data down the tree. The useReducer hook manages complex state logic with actions and reducers. Redux provides centralized state management with unidirectional data flow. Actions describe what happened, reducers specify state updates, and store holds application state. The useSelector hook reads state, while useDispatch dispatches actions. Modern Redux Toolkit simplifies boilerplate with createSlice. Proper state management improves maintainability.