JAVASCRIPT

Advanced State Management with useReducer Hook

Learn to manage complex application state more predictably with React's `useReducer` hook. This snippet illustrates building a simple shopping cart with multiple actions (add, remove, update quantity).

import React, { useReducer } from 'react';

const initialState = {
  cart: [],
  totalItems: 0,
  totalPrice: 0,
};

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      {
        const existingItemIndex = state.cart.findIndex(item => item.id === action.payload.id);
        if (existingItemIndex > -1) {
          const updatedCart = state.cart.map((item, index) => 
            index === existingItemIndex 
              ? { ...item, quantity: item.quantity + 1 } 
              : item
          );
          return {
            ...state,
            cart: updatedCart,
            totalItems: state.totalItems + 1,
            totalPrice: state.totalPrice + action.payload.price,
          };
        } else {
          return {
            ...state,
            cart: [...state.cart, { ...action.payload, quantity: 1 }],
            totalItems: state.totalItems + 1,
            totalPrice: state.totalPrice + action.payload.price,
          };
        }
      }
    case 'REMOVE_ITEM':
      {
        const itemToRemove = state.cart.find(item => item.id === action.payload.id);
        if (!itemToRemove) return state;

        return {
          ...state,
          cart: state.cart.filter(item => item.id !== action.payload.id),
          totalItems: state.totalItems - itemToRemove.quantity,
          totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity),
        };
      }
    case 'UPDATE_QUANTITY':
      {
        const updatedCart = state.cart.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: action.payload.quantity }
            : item
        );
        const newTotalItems = updatedCart.reduce((sum, item) => sum + item.quantity, 0);
        const newTotalPrice = updatedCart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
        return {
          ...state,
          cart: updatedCart,
          totalItems: newTotalItems,
          totalPrice: newTotalPrice,
        };
      }
    default:
      return state;
  }
}

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  const products = [
    { id: 'p1', name: 'Laptop', price: 1200 },
    { id: 'p2', name: 'Mouse', price: 25 },
    { id: 'p3', name: 'Keyboard', price: 75 },
  ];

  return (
    <div>
      <h2>Shopping Cart ({state.totalItems} items)</h2>
      <h3>Total Price: ${state.totalPrice.toFixed(2)}</h3>
      <div>
        <h4>Available Products:</h4>
        {products.map(product => (
          <div key={product.id}>
            {product.name} (${product.price.toFixed(2)})
            <button onClick={() => dispatch({ type: 'ADD_ITEM', payload: product })}>
              Add to Cart
            </button>
          </div>
        ))}
      </div>
      <hr />
      <h4>Your Cart:</h4>
      {state.cart.length === 0 ? (
        <p>Cart is empty.</p>
      ) : (
        <ul>
          {state.cart.map(item => (
            <li key={item.id}>
              {item.name} x {item.quantity} (${(item.price * item.quantity).toFixed(2)})
              <button onClick={() => dispatch({ type: 'REMOVE_ITEM', payload: { id: item.id } })}>
                Remove All
              </button>
              <button onClick={() => dispatch({ type: 'UPDATE_QUANTITY', payload: { id: item.id, quantity: Math.max(1, item.quantity - 1) } })}>
                -1
              </button>
              <button onClick={() => dispatch({ type: 'UPDATE_QUANTITY', payload: { id: item.id, quantity: item.quantity + 1 } })}>
                +1
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default ShoppingCart;
How it works: This snippet demonstrates managing complex state logic using the `useReducer` hook. Instead of multiple `useState` calls, a single `cartReducer` function handles all state transitions for a shopping cart, based on dispatched `action` objects. This makes state updates more predictable, centralized, and easier to test, especially when the next state depends on the previous state or involves multiple related values like `cart` items, `totalItems`, and `totalPrice`.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs