Overview

Proper error handling is crucial for a robust storefront experience. This guide covers common error scenarios and how to handle them gracefully.

Common Error Scenarios

1. Network Errors

try {
  const response = await fetch(`${API_BASE}/locations/${locationId}/products`, {
    headers: { 'X-API-Key': API_KEY }
  });
} catch (error) {
  if (error.name === 'TypeError') {
    // Network error - show offline message
    showOfflineMessage();
  }
}

2. API Response Errors

const handleAPIResponse = async (response) => {
  if (!response.ok) {
    const error = await response.json().catch(() => ({ 
      message: 'Unknown error occurred' 
    }));
    
    switch (response.status) {
      case 401:
        throw new Error('Invalid API key');
      case 404:
        throw new Error('Resource not found');
      case 429:
        throw new Error('Rate limit exceeded. Please try again later.');
      case 500:
        throw new Error('Server error. Please try again.');
      default:
        throw new Error(error.message || 'Request failed');
    }
  }
  return response.json();
};

3. Cart State Errors

const addToCart = async (product, quantity) => {
  try {
    const updatedCart = await addItemToCart(locationId, cartId, product, quantity);
    setCart(updatedCart);
  } catch (error) {
    // Handle specific cart errors
    if (error.message.includes('out of stock')) {
      showOutOfStockMessage(product.name);
    } else if (error.message.includes('minimum order')) {
      showMinimumOrderMessage();
    } else {
      showGenericErrorMessage();
    }
  }
};

Error Recovery Strategies

Retry Logic

const retryRequest = async (fn, maxRetries = 3) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
    }
  }
};

Fallback Data

const fetchMenuWithFallback = async (locationId) => {
  try {
    return await fetchMenu(locationId);
  } catch (error) {
    console.warn('Failed to fetch menu, using cached data:', error);
    return getCachedMenu(locationId) || getStaticMenuData();
  }
};

Rate Limiting

The API has a rate limit of 200 requests per 10 minutes. Handle rate limiting gracefully:
const rateLimitedRequest = async (requestFn) => {
  try {
    return await requestFn();
  } catch (error) {
    if (error.message.includes('rate limit')) {
      // Wait and retry after rate limit period
      await new Promise(resolve => setTimeout(resolve, 60000));
      return requestFn();
    }
    throw error;
  }
};

User Experience Best Practices

Loading States

const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const loadProducts = async () => {
  setLoading(true);
  setError(null);
  
  try {
    const products = await fetchProducts(locationId);
    setProducts(products);
  } catch (err) {
    setError(err.message);
  } finally {
    setLoading(false);
  }
};

Error Boundaries

class APIErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>Please refresh the page or try again later.</p>
          <button onClick={() => window.location.reload()}>
            Refresh Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}