# Frontend Exception Handling Guide ## Understanding Your API's Exception Structure Your API returns consistent error responses with this structure: ```json { "error_code": "PRODUCT_NOT_FOUND", "message": "MarketplaceProduct with ID 'ABC123' not found", "status_code": 404, "field": "marketplace_product_id", "details": { "resource_type": "MarketplaceProduct", "identifier": "ABC123" } } ``` ## Frontend Error Handling Strategy ### 1. HTTP Client Configuration Configure your HTTP client to handle error responses consistently: #### Axios Example ```javascript // api/client.js import axios from 'axios'; const apiClient = axios.create({ baseURL: 'https://your-api.com/api/v1', timeout: 10000, }); // Response interceptor for error handling apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.data) { // Transform API error to standardized format const apiError = { errorCode: error.response.data.error_code, message: error.response.data.message, statusCode: error.response.data.status_code, field: error.response.data.field, details: error.response.data.details, originalError: error }; throw apiError; } // Handle network errors if (error.code === 'ECONNABORTED') { throw { errorCode: 'NETWORK_TIMEOUT', message: 'Request timed out. Please try again.', statusCode: 408 }; } throw { errorCode: 'NETWORK_ERROR', message: 'Network error. Please check your connection.', statusCode: 0 }; } ); export default apiClient; ``` #### Fetch Example ```javascript // api/client.js class ApiClient { constructor(baseURL) { this.baseURL = baseURL; } async request(endpoint, options = {}) { try { const response = await fetch(`${this.baseURL}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers, }, ...options, }); const data = await response.json(); if (!response.ok) { throw { errorCode: data.error_code, message: data.message, statusCode: data.status_code, field: data.field, details: data.details, }; } return data; } catch (error) { if (error.errorCode) { throw error; // Re-throw API errors } // Handle network errors throw { errorCode: 'NETWORK_ERROR', message: 'Failed to connect to server', statusCode: 0 }; } } } export const apiClient = new ApiClient('https://your-api.com/api/v1'); ``` ### 2. Error Code Mapping Create mappings for user-friendly messages: ```javascript // constants/errorMessages.js export const ERROR_MESSAGES = { // Authentication errors INVALID_CREDENTIALS: 'Invalid username or password. Please try again.', TOKEN_EXPIRED: 'Your session has expired. Please log in again.', USER_NOT_ACTIVE: 'Your account has been deactivated. Contact support.', // MarketplaceProduct errors PRODUCT_NOT_FOUND: 'MarketplaceProduct not found. It may have been removed.', PRODUCT_ALREADY_EXISTS: 'A product with this ID already exists.', INVALID_PRODUCT_DATA: 'Please check the product information and try again.', // Inventory errors INSUFFICIENT_INVENTORY: 'Not enough inventory available for this operation.', INVENTORY_NOT_FOUND: 'No inventory information found for this product.', NEGATIVE_INVENTORY_NOT_ALLOWED: 'Inventory quantity cannot be negative.', // Shop errors SHOP_NOT_FOUND: 'Shop not found or no longer available.', UNAUTHORIZED_SHOP_ACCESS: 'You do not have permission to access this shop.', SHOP_ALREADY_EXISTS: 'A shop with this code already exists.', MAX_SHOPS_REACHED: 'You have reached the maximum number of shops allowed.', // Import errors IMPORT_JOB_NOT_FOUND: 'Import job not found.', IMPORT_JOB_CANNOT_BE_CANCELLED: 'This import job cannot be cancelled at this time.', MARKETPLACE_CONNECTION_FAILED: 'Failed to connect to marketplace. Please try again.', // Generic fallbacks VALIDATION_ERROR: 'Please check your input and try again.', NETWORK_ERROR: 'Connection error. Please check your internet connection.', NETWORK_TIMEOUT: 'Request timed out. Please try again.', INTERNAL_SERVER_ERROR: 'Something went wrong. Please try again later.', }; export const getErrorMessage = (errorCode, fallbackMessage = null) => { return ERROR_MESSAGES[errorCode] || fallbackMessage || 'An unexpected error occurred.'; }; ``` ### 3. Component Error Handling #### React Hook for Error Handling ```javascript // hooks/useApiError.js import { useState } from 'react'; import { getErrorMessage } from '../constants/errorMessages'; export const useApiError = () => { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const handleApiCall = async (apiCall) => { setIsLoading(true); setError(null); try { const result = await apiCall(); setIsLoading(false); return result; } catch (apiError) { setIsLoading(false); setError({ code: apiError.errorCode, message: getErrorMessage(apiError.errorCode, apiError.message), field: apiError.field, details: apiError.details }); throw apiError; // Re-throw for component-specific handling } }; const clearError = () => setError(null); return { error, isLoading, handleApiCall, clearError }; }; ``` #### Form Error Handling ```javascript // components/ProductForm.jsx import React, { useState } from 'react'; import { useApiError } from '../hooks/useApiError'; import { createProduct } from '../api/products'; const ProductForm = () => { const [formData, setFormData] = useState({ product_id: '', name: '', price: '' }); const [fieldErrors, setFieldErrors] = useState({}); const { error, isLoading, handleApiCall, clearError } = useApiError(); const handleSubmit = async (e) => { e.preventDefault(); setFieldErrors({}); clearError(); try { await handleApiCall(() => createProduct(formData)); // Success handling alert('MarketplaceProduct created successfully!'); setFormData({ product_id: '', name: '', price: '' }); } catch (apiError) { // Handle field-specific errors if (apiError.field) { setFieldErrors({ [apiError.field]: apiError.message }); } // Handle specific error codes switch (apiError.errorCode) { case 'PRODUCT_ALREADY_EXISTS': setFieldErrors({ product_id: 'This product ID is already taken' }); break; case 'INVALID_PRODUCT_DATA': if (apiError.field) { setFieldErrors({ [apiError.field]: apiError.message }); } break; default: // Generic error is handled by useApiError hook break; } } }; return (
); }; ``` ### 4. Global Error Handler #### React Error Boundary ```javascript // components/ErrorBoundary.jsx import React from 'react'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('Error caught by boundary:', error, errorInfo); // Log to error reporting service if (window.errorReporting) { window.errorReporting.captureException(error, { extra: errorInfo }); } } render() { if (this.state.hasError) { return (We're sorry, but something unexpected happened.