Skip to content

API Optimization Guide

Preventing Duplicate API Calls

One common performance issue in frontend applications is making redundant or duplicate API calls for the same data. This can happen for various reasons:

  1. Multiple components requesting the same data independently
  2. Re-renders causing repeated data fetching
  3. Missing caching mechanisms
  4. Inefficient state management

Problem Identified

On the dashboard page, we identified multiple redundant API calls being made on a single page load:

  • Multiple calls to authentication endpoints
  • Repeated calls to the /trackers/status-counts endpoint
  • Duplicate calls to the trackers list endpoint

These redundant calls were causing:

  • Increased server load
  • Slower page load times
  • Unnecessary network traffic
  • Potential race conditions

Solution: React Query Implementation

We've implemented React Query (TanStack Query) to solve this problem. React Query provides:

  1. Automatic caching - Data is cached by query key and reused across components
  2. Deduplication - Multiple components requesting the same data will share a single request
  3. Stale-while-revalidate - Shows cached data immediately while fetching fresh data in the background
  4. Configurable staleness - Control how long data remains "fresh" before refetching

Key Implementation Details

// Use React Query to fetch status counts
const {
  data: statusCounts = {
    CREATED: 0,
    IN_TRANSIT: 0,
    IN_STORAGE: 0,
    DELIVERED: 0,
  },
  isLoading: isStatusCountsLoading,
  error: statusCountsError,
} = useQuery({
  queryKey: ["statusCounts"],
  queryFn: trackersApi.getStatusCounts,
  staleTime: 60000, // 1 minute
});

// Use React Query to fetch trackers
const {
  data: trackersResponse,
  isLoading: isTrackersLoading,
  error: trackersError,
} = useQuery({
  queryKey: ["trackers", { limit: 50, skip: 0, include_latest_location: true }],
  queryFn: () =>
    trackersApi.getTrackers({
      limit: 50,
      skip: 0,
      include_latest_location: true,
    }),
  staleTime: 60000, // 1 minute
});

Key Concepts

  1. Query Keys: Unique identifiers for cached data. We use array-based keys like ['statusCounts'] or ['trackers', { params }] to identify queries.

  2. staleTime: Controls how long data remains "fresh" before triggering a background refetch. We've set this to 60 seconds (60000ms) for most queries.

  3. Conditional Fetching: Using the enabled option to only fetch data when needed:

useQuery({
  queryKey: ["locationHistory", selectedTracker],
  queryFn: async () => {
    /* fetch logic */
  },
  enabled: isModalOpen && !!selectedTracker,
  staleTime: 60000,
});

Benefits

  • Reduced API Calls: Multiple components can use the same data without triggering duplicate requests
  • Improved Performance: Faster page loads and better user experience
  • Automatic Loading States: Built-in loading and error states simplify component logic
  • Consistent Data: All components display the same data from the cache

Best Practices

  1. Use Consistent Query Keys: Maintain a consistent pattern for query keys across the application
  2. Configure Appropriate Stale Times: Set stale times based on how frequently data changes
  3. Prefetch Critical Data: Use prefetchQuery for data that will be needed soon
  4. Invalidate Queries After Mutations: Call queryClient.invalidateQueries() after data changes

Implementation Checklist

  • Install and configure React Query
  • Refactor API calls to use useQuery hooks
  • Implement proper query keys
  • Configure appropriate stale times
  • Add conditional fetching where needed
  • Test to ensure duplicate calls are eliminated

Monitoring and Verification

To verify the optimization is working:

  1. Open Chrome DevTools
  2. Go to the Network tab
  3. Filter by XHR/Fetch requests
  4. Load the dashboard page
  5. Verify that each endpoint is only called once (unless data is explicitly refetched)

Additional Resources