REACT Lesson 33 – Mini Project - API Integration | Dataplexa
LESSON 33

Mini Project – API Integration

Build DataFlow's live analytics dashboard by connecting to real APIs, handling loading states, and managing asynchronous data updates.

APIs power every dashboard you've used. Netflix pulls your viewing history. Airbnb fetches property listings. Your DataFlow dashboard needs real data from backend services. React treats API calls like async conversations. Your component asks for data, shows a loading spinner while waiting, then updates the UI when data arrives. But networks fail, APIs go down, and users click buttons faster than servers respond.

Why APIs Matter in React

Static data dies quickly. Your dashboard shows yesterday's revenue forever. Users lose trust. Business decisions fail. Real APIs give you live data. Sales update every minute. User counts climb in real-time. Your DataFlow dashboard becomes a living business pulse. But APIs bring complexity. Network delays, failed requests, inconsistent data formats. React's component lifecycle needs careful API orchestration.

The DataFlow Challenge

Your analytics dashboard currently shows mock data. The business team needs real metrics from three APIs: sales revenue, user activity, and order tracking. Each API has different response times and error patterns.

Basic API Integration

Start simple. One API call, one piece of data. The useEffect hook handles the timing. When your component mounts, fetch data.
// DataFlow revenue widget - basic API call
import { useState, useEffect } from 'react';

function RevenueWidget() {
  const [revenue, setRevenue] = useState(0);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/revenue/today')
      .then(response => response.json())
      .then(data => {
        setRevenue(data.total);
        setLoading(false);
      });
  }, []); // Empty array = run once on mount

  return (
    <div style={{padding: '20px', border: '2px solid #e2e8f0'}}>
      <h3>Today's Revenue</h3>
      {loading ? <p>Loading...</p> : <p>${revenue.toLocaleString()}</p>}
    </div>
  );
}
DataFlow Dashboard

What just happened?

The component mounted, triggered useEffect, simulated an API call, and updated state. Loading disappeared when data arrived. Try refreshing to see the loading state again.

The pattern repeats everywhere. State for data, state for loading, useEffect for the API call. But real APIs need error handling.

Error Handling and Loading States

Networks fail. APIs return errors. Users need feedback beyond eternal loading spinners. Three states matter: loading, success, error. Your component handles all three gracefully.
// DataFlow stats with complete error handling
function StatsOverview() {
  const [stats, setStats] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchDashboardStats();
  }, []);

  const fetchDashboardStats = async () => {
    try {
      setLoading(true);
      setError(null);
      
      const response = await fetch('/api/stats/overview');
      if (!response.ok) throw new Error('Failed to load stats');
      
      const data = await response.json();
      setStats(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Loading dashboard...</div>;
  if (error) return <div style={{color: 'red'}}>Error: {error}</div>;

  return (
    <div>
      <h2>DataFlow Overview</h2>
      <p>Revenue: ${stats?.revenue || 0}</p>
      <p>Users: {stats?.users || 0}</p>
      <button onClick={fetchDashboardStats}>Refresh</button>
    </div>
  );
}
DataFlow Dashboard

What just happened?

Three states managed cleanly: loading shows first, then success with data, or error with message. The Refresh button lets users retry failed requests. Click it twice to see the error state.

Async/await reads cleaner than Promise chains. Try/catch/finally handles all scenarios. Your users never wonder what's happening.

Multiple API Calls and Data Management

Real dashboards pull from multiple APIs. Sales from one service, users from another, orders from a third. Each API has different speeds and failure rates. Parallel calls load faster than sequential. But managing multiple loading states gets complex. Custom hooks clean up the mess.
// Custom hook for DataFlow API management
function useDashboardData() {
  const [data, setData] = useState({
    revenue: null,
    users: null,
    orders: null
  });
  const [loading, setLoading] = useState(true);
  const [errors, setErrors] = useState({});

  useEffect(() => {
    loadAllData();
  }, []);

  const loadAllData = async () => {
    setLoading(true);
    setErrors({});

    const apiCalls = [
      { key: 'revenue', url: '/api/revenue' },
      { key: 'users', url: '/api/users/count' },
      { key: 'orders', url: '/api/orders/today' }
    ];

    const results = await Promise.allSettled(
      apiCalls.map(async ({ key, url }) => {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`${key} API failed`);
        return { key, data: await response.json() };
      })
    );

    const newData = { ...data };
    const newErrors = {};

    results.forEach((result, index) => {
      const { key } = apiCalls[index];
      if (result.status === 'fulfilled') {
        newData[key] = result.value.data;
      } else {
        newErrors[key] = result.reason.message;
      }
    });

    setData(newData);
    setErrors(newErrors);
    setLoading(false);
  };

  return { data, loading, errors, refresh: loadAllData };
}
DataFlow Dashboard
Promise.allSettled() runs all API calls in parallel. Some succeed, others fail. Your dashboard shows partial data instead of total failure.

Performance Warning

Every API call costs money and server resources. Cache responses when possible. Use React Query or SWR for production apps with complex data needs.

Real-Time Updates and WebSockets

Some dashboard data needs live updates. Stock prices, chat messages, live user counts. REST API polling wastes bandwidth. WebSockets stream data efficiently. React components connect to WebSocket servers, listen for messages, update state automatically. But WebSocket connections need cleanup to prevent memory leaks.
// DataFlow live metrics with WebSocket connection
function LiveMetrics() {
  const [metrics, setMetrics] = useState({
    activeUsers: 0,
    liveRevenue: 0,
    ordersToday: 0
  });
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    // Mock WebSocket connection for demo
    let interval;
    
    const connectWebSocket = () => {
      setConnected(true);
      
      // Simulate live data updates every 2 seconds
      interval = setInterval(() => {
        setMetrics(prev => ({
          activeUsers: prev.activeUsers + Math.floor(Math.random() * 10) - 4,
          liveRevenue: prev.liveRevenue + Math.floor(Math.random() * 500),
          ordersToday: prev.ordersToday + Math.floor(Math.random() * 3)
        }));
      }, 2000);
    };

    connectWebSocket();

    // Cleanup function - runs when component unmounts
    return () => {
      setConnected(false);
      if (interval) clearInterval(interval);
    };
  }, []);

  return (
    <div style={{padding: '20px', border: '2px solid #e2e8f0'}}>
      <div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
        <h2>Live DataFlow Metrics</h2>
        <div style={{
          width: '10px', 
          height: '10px', 
          borderRadius: '50%',
          background: connected ? '#22c55e' : '#ef4444'
        }}></div>
      </div>
      <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px'}}>
        <div>Active Users: {Math.max(0, metrics.activeUsers)}</div>
        <div>Live Revenue: ${metrics.liveRevenue}</div>
        <div>Orders Today: {Math.max(0, metrics.ordersToday)}</div>
      </div>
    </div>
  );
}
DataFlow Dashboard

What just happened?

The component simulated a WebSocket connection with setInterval, updating metrics every 2 seconds. The green dot shows connection status. The cleanup function prevents memory leaks when the component unmounts.

The cleanup function in useEffect prevents memory leaks. Without it, intervals and WebSocket connections survive component unmounting. Your app slows down, crashes eventually.

Complete Dashboard Integration

Time to combine everything. Multiple API calls, error handling, live updates, user interactions. Your DataFlow dashboard becomes a real business tool.
// Complete DataFlow dashboard with all integrations
function DataFlowDashboard() {
  const [data, setData] = useState({
    stats: null,
    chartData: null,
    recentOrders: null
  });
  const [loading, setLoading] = useState(true);
  const [lastUpdate, setLastUpdate] = useState(null);

  useEffect(() => {
    loadDashboardData();
    
    // Refresh every 5 minutes
    const refreshInterval = setInterval(loadDashboardData, 300000);
    
    return () => clearInterval(refreshInterval);
  }, []);

  const loadDashboardData = async () => {
    try {
      const [statsRes, chartRes, ordersRes] = await Promise.all([
        fetch('/api/dashboard/stats'),
        fetch('/api/dashboard/chart-data'),
        fetch('/api/orders/recent')
      ]);

      // Simulate API responses with realistic data
      const mockData = {
        stats: {
          revenue: 892340,
          users: 24680,
          orders: 1534,
          growth: 12.5
        },
        chartData: [
          { month: 'Jan', value: 65000 },
          { month: 'Feb', value: 75000 },
          { month: 'Mar', value: 85000 },
          { month: 'Apr', value: 95000 }
        ],
        recentOrders: [
          { id: 'ORD-001', customer: 'Alice Johnson', amount: 299.99 },
          { id: 'ORD-002', customer: 'Bob Smith', amount: 149.50 },
          { id: 'ORD-003', customer: 'Carol Davis', amount: 449.00 }
        ]
      };

      setData(mockData);
      setLastUpdate(new Date().toLocaleTimeString());
    } catch (error) {
      console.error('Dashboard load failed:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <div style={{padding: '40px', textAlign: 'center'}}>
        <h2>Loading DataFlow Dashboard...</h2>
      </div>
    );
  }

  return (
    <div style={{padding: '20px', maxWidth: '1200px'}}>
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px'}}>
        <h1 style={{color: '#047857', margin: 0}}>DataFlow Dashboard</h1>
        <div style={{fontSize: '14px', color: '#64748b'}}>
          Last updated: {lastUpdate}
        </div>
      </div>
      
      {/* KPI Cards */}
      <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '16px', marginBottom: '24px'}}>
        <div style={{padding: '16px', background: '#f0fdf4', borderRadius: '8px', border: '1px solid #bbf7d0'}}>
          <div style={{fontSize: '12px', color: '#166534', marginBottom: '4px'}}>REVENUE</div>
          <div style={{fontSize: '24px', fontWeight: 'bold', color: '#15803d'}}>${data.stats?.revenue?.toLocaleString()}</div>
        </div>
        <div style={{padding: '16px', background: '#eff6ff', borderRadius: '8px', border: '1px solid #bfdbfe'}}>
          <div style={{fontSize: '12px', color: '#1e40af', marginBottom: '4px'}}>USERS</div>
          <div style={{fontSize: '24px', fontWeight: 'bold', color: '#1d4ed8'}}>{data.stats?.users?.toLocaleString()}</div>
        </div>
        <div style={{padding: '16px', background: '#fff7ed', borderRadius: '8px', border: '1px solid #fed7aa'}}>
          <div style={{fontSize: '12px', color: '#c2410c', marginBottom: '4px'}}>ORDERS</div>
          <div style={{fontSize: '24px', fontWeight: 'bold', color: '#ea580c'}}>{data.stats?.orders?.toLocaleString()}</div>
        </div>
        <div style={{padding: '16px', background: '#faf5ff', borderRadius: '8px', border: '1px solid #e9d5ff'}}>
          <div style={{fontSize: '12px', color: '#7c2d12', marginBottom: '4px'}}>GROWTH</div>
          <div style={{fontSize: '24px', fontWeight: 'bold', color: '#a855f7'}}>+{data.stats?.growth}%</div>
        </div>
      </div>

      {/* Recent Orders */}
      <div style={{background: 'white', border: '1px solid #e2e8f0', borderRadius: '8px', padding: '20px'}}>
        <h3 style={{margin: '0 0 16px', color: '#0f172a'}}>Recent Orders</h3>
        {data.recentOrders?.map(order => (
          <div key={order.id} style={{display: 'flex', justifyContent: 'space-between', padding: '8px 0', borderBottom: '1px solid #f1f5f9'}}>
            <div>
              <div style={{fontWeight: '600'}}>{order.id}</div>
              <div style={{fontSize: '14px', color: '#64748b'}}>{order.customer}</div>
            </div>
            <div style={{fontWeight: '600', color: '#047857'}}>${order.amount}</div>
          </div>
        ))}
      </div>
      
      <button 
        onClick={loadDashboardData} 
        style={{
          marginTop: '20px',
          background: '#047857',
          color: 'white',
          border: 'none',
          padding: '10px 20px',
          borderRadius: '6px',
          cursor: 'pointer'
        }}
      >
        Refresh Dashboard
      </button>
    </div>
  );
}
DataFlow Dashboard
This dashboard handles everything. Multiple API calls, loading states, automatic refresh intervals, manual refresh buttons. Your DataFlow app feels professional, responsive, reliable. Real dashboards add authentication headers, retry logic, offline caching. But the foundation stays the same: useEffect for timing, useState for data, cleanup functions for resources.

Quiz

1. The DataFlow dashboard needs to load revenue data when the component first renders. Which approach ensures the API call happens at the right time?


2. Your DataFlow dashboard needs data from three different APIs: revenue, users, and orders. One API might fail while others succeed. What's the best approach?


3. DataFlow's live metrics component uses setInterval to update user counts every 2 seconds. What prevents memory leaks when the component unmounts?


Up Next: React Case Study

Analyze how Netflix built their dashboard, Facebook handles millions of API calls, and Linear creates buttery-smooth real-time updates.