Global Error Handling Examples

1. React App - Complete Setup

import ApiClient, { parseResponseMessage } from "hmm-api";
import { toast } from "sonner";

// Configure ONCE - handles ALL API calls
export const api = new ApiClient({
  baseUrl: "https://api.example.com",
  returnParsedError: true,
  onError: (response) => {
    // Handle ALL errors globally
    toast.error(response.error);

    // Global auth handling
    if (response.status === 401) {
      localStorage.removeItem("token");
      window.location.href = "/login";
    }

    // Global server error handling
    if (response.status >= 500) {
      toast.error("Server error. Please try again later.");
    }
  },
  onSuccess: (response) => {
    // Optional: Global success feedback
    console.log("✅ Request successful");
  },
});

// Usage - No error handling needed anywhere!
const App = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  // GET request - errors handled automatically
  const fetchUsers = async () => {
    setLoading(true);
    const response = await api.get("/users", {
      finally: () => setLoading(false),
    });

    if (response.success) {
      setUsers(response.data);
    }
  };

  // POST request - errors handled automatically
  const createUser = async (userData) => {
    const response = await api.post("/users", userData);

    if (response.success) {
      setUsers((prev) => [...prev, response.data]);
      toast.success("User created!");
    }
  };

  return <div>{/* Your UI */}</div>;
};

2. Form Validation - Global Handling

import ApiClient, { parseResponseMessage } from "hmm-api";
import { toast } from "@/hooks/use-toast";

// Configure for form validation
export const api = new ApiClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,
  returnParsedError: true, // Clean validation messages
  onError: (response) => {
    // Handle ALL form errors globally
    if (response.status === 422) {
      toast({
        title: "Validation Error",
        description: response.error,
        variant: "destructive",
      });
    } else if (response.status === 401) {
      window.location.href = "/login";
    } else {
      toast({
        title: "Error",
        description: response.error,
        variant: "destructive",
      });
    }
  },
});

// Usage - No error handling needed!
const ContactForm = () => {
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (formData) => {
    setIsSubmitting(true);

    const response = await api.post("/contact", formData, {
      finally: () => setIsSubmitting(false),
    });

    if (response.success) {
      toast({ title: "Success!", description: "Message sent!" });
      resetForm();
    }
    // No error handling needed - global config handles it!
  };

  return (
    <form onSubmit={handleSubmit}>
      <button disabled={isSubmitting}>
        {isSubmitting ? "Sending..." : "Send"}
      </button>
    </form>
  );
};

3. Using parseResponseMessage Utility

import { parseResponseMessage } from "hmm-api";

// Use in your own error handling
const customErrorHandler = (error) => {
  const parsed = parseResponseMessage(error);

  // Now you have a clean message
  console.log(parsed.message); // "User not found" instead of complex object

  // Use in your UI
  showErrorDialog(parsed.message);
  setErrorMessage(parsed.message);
};

// Example: Custom validation
const validateForm = (serverError) => {
  const { message } = parseResponseMessage(serverError);

  // Clean message for user display
  setFieldError(message);
};

// Example: Logging
const logError = (error) => {
  const { message } = parseResponseMessage(error);

  logger.error("API Error", {
    cleanMessage: message,
    originalError: error,
  });
};

4. Authentication - Global Handling

import ApiClient from "hmm-api";
import { toast } from "react-hot-toast";

// Configure auth handling globally
const api = new ApiClient({
  baseUrl: "https://api.example.com",
  credentials: "include",
  returnParsedError: true,
  onError: (response) => {
    // Handle ALL auth errors globally
    if (response.status === 401) {
      localStorage.removeItem("token");
      toast.error("Session expired. Please login again.");
      window.location.href = "/login";
    } else if (response.status === 403) {
      toast.error("Access denied.");
      window.location.href = "/dashboard";
    } else {
      toast.error(response.error);
    }
  },
});

// Usage - Auth handled automatically everywhere!
const Dashboard = () => {
  const [profile, setProfile] = useState(null);
  const [orders, setOrders] = useState([]);

  useEffect(() => {
    // Both requests protected automatically
    loadProfile();
    loadOrders();
  }, []);

  const loadProfile = async () => {
    const response = await api.get("/user/profile");
    if (response.success) {
      setProfile(response.data);
    }
    // No auth error handling needed!
  };

  const loadOrders = async () => {
    const response = await api.get("/user/orders");
    if (response.success) {
      setOrders(response.data);
    }
    // No auth error handling needed!
  };

  return <div>{/* Your protected UI */}</div>;
};

5. File Upload with Progress (New Feature)

import ApiClient from "hmm-api";
import { toast } from "sonner";

const api = new ApiClient({
  baseUrl: "https://api.example.com",
  timeout: 60000, // 1 minute for uploads
  onError: (response) => {
    toast.error(response.error);
  },
});

const FileUploader = () => {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);

  const uploadFile = async (file: File) => {
    setUploading(true);
    const formData = new FormData();
    formData.append("file", file);

    const response = await api.post("/upload", formData, {
      onUploadProgress: (progress) => {
        setProgress(progress.percentage);
      },
      finally: () => setUploading(false),
    });

    if (response.success) {
      toast.success("File uploaded!");
      setProgress(0);
    }
  };

  return (
    <div>
      <input type="file" onChange={(e) => uploadFile(e.target.files[0])} />
      {uploading && <ProgressBar value={progress} />}
    </div>
  );
};

6. Real-time Polling (New Feature)

import ApiClient from "hmm-api";

const api = new ApiClient({
  baseUrl: "https://api.example.com",
  onError: (response) => {
    console.error("Polling error:", response.error);
  },
});

const OrderTracker = () => {
  const [orderStatus, setOrderStatus] = useState(null);
  const [polling, setPolling] = useState(null);

  const startTracking = async (orderId: string) => {
    const result = await api.get(`/orders/${orderId}`, {
      poll: {
        interval: 5000, // Check every 5 seconds
        stopCondition: (response) => {
          return response.data?.status === "delivered";
        },
        onPollSuccess: (response) => {
          setOrderStatus(response.data);
        },
      },
    });

    // Store the stop function if it's a polling result
    if ("stop" in result) {
      setPolling({ stop: result.stop });
    }
  };

  const stopTracking = () => {
    if (polling) {
      polling.stop();
      setPolling(null);
    }
  };

  return (
    <div>
      <button onClick={() => startTracking("123")}>Start Tracking</button>
      <button onClick={stopTracking}>Stop</button>
      <OrderStatus status={orderStatus} />
    </div>
  );
};

Key Benefits of Global Error Handling + New Features

Before hmm-api v2.0.0 (Repetitive)

// Every request needs error handling
const response1 = await fetch("/users");
if (!response1.ok) {
  toast.error("Failed to load users");
  if (response1.status === 401) redirectToLogin();
}

const response2 = await fetch("/posts");
if (!response2.ok) {
  toast.error("Failed to load posts");
  if (response2.status === 401) redirectToLogin();
}
// Repeat for every API call...

After hmm-api v2.0.0 (Global + New Features)

// Configure once
const api = new ApiClient({
  timeout: 10000, // Global timeout
  onError: (response) => {
    toast.error(response.error);
    if (response.status === 401) redirectToLogin();
  },
});

// Clean usage everywhere - with new features!
const users = await api.get("/users");
const posts = await api.get("/posts");
const upload = await api.post("/upload", formData, {
  onUploadProgress: (p) => console.log(`${p.percentage}%`),
});

// Automatic polling
const result = await api.get("/status", {
  poll: {
    interval: 5000,
    stopCondition: (r) => r.data.ready,
  },
});

New Features in v2.0.0

  1. Polling - Automatic data fetching with stop conditions
  2. Upload Progress - Track file upload progress like Axios
  3. Download Progress - Monitor download progress
  4. Timeout Support - Global and per-request timeouts
  5. Global Error Handling - Configure once, benefit everywhere

7. Multiple Independent Polling Operations

import ApiClient from "hmm-api";

const api = new ApiClient({
  baseUrl: "https://api.example.com",
  onError: (response) => {
    console.error("API Error:", response.error);
  },
});

const Dashboard = () => {
  const [orderStatus, setOrderStatus] = useState(null);
  const [inventoryStatus, setInventoryStatus] = useState(null);
  const [systemHealth, setSystemHealth] = useState(null);

  useEffect(() => {
    // Start multiple independent polling operations simultaneously
    startMultiplePolling();
  }, []);

  const startMultiplePolling = async () => {
    // All three polling operations run independently
    const [orderResult, inventoryResult, healthResult] = await Promise.all([
      // Poll 1: Order status every 5 seconds
      api.get("/orders/status", {
        poll: {
          interval: 5000,
          maxAttempts: 20,
          onPollSuccess: (response, attempt) => {
            setOrderStatus(response.data);
            console.log(`Order poll attempt ${attempt}`);
          },
          stopCondition: (response) => {
            return response.data?.allOrdersProcessed === true;
          },
        },
      }),

      // Poll 2: Inventory levels every 10 seconds
      api.get("/inventory/levels", {
        poll: {
          interval: 10000,
          maxAttempts: 15,
          onPollSuccess: (response, attempt) => {
            setInventoryStatus(response.data);
            console.log(`Inventory poll attempt ${attempt}`);
          },
          stopCondition: (response) => {
            return response.data?.lowStockItems?.length === 0;
          },
        },
      }),

      // Poll 3: System health every 3 seconds
      api.get("/system/health", {
        poll: {
          interval: 3000,
          maxAttempts: 30,
          onPollSuccess: (response, attempt) => {
            setSystemHealth(response.data);
            console.log(`Health poll attempt ${attempt}`);
          },
          stopCondition: (response) => {
            return response.data?.status === "all_systems_operational";
          },
        },
      }),
    ]);

    // Each polling operation completes independently
    console.log("All polling operations completed!");
  };

  return (
    <div>
      <OrderStatusWidget status={orderStatus} />
      <InventoryWidget status={inventoryStatus} />
      <SystemHealthWidget status={systemHealth} />
    </div>
  );
};

Key Benefits: Independent Polling Operations

  • No Interference: Each polling operation runs independently with its own interval and conditions
  • Different Intervals: Poll 1 (5s), Poll 2 (10s), Poll 3 (3s) - all work simultaneously
  • Individual Stop Conditions: Each poll can stop based on its own criteria
  • Separate Callbacks: Each polling operation has its own success/error handling
  • Parallel Execution: All polling operations run concurrently using Promise.all

Configure once, benefit everywhere with powerful new features!