REACT Lesson 19 – API Calls with Fetch | Dataplexa
LESSON 19

API Calls with Fetch

Build dynamic React components that pull live data from APIs using JavaScript's built-in fetch function.

API calls transform static React components into dynamic applications. Every modern web app — Netflix loading movie data, Airbnb fetching property listings, or Linear syncing project updates — relies on API communication. Your DataFlow dashboard currently displays hardcoded analytics data. Real dashboards pull fresh numbers from servers. Today you'll connect React components to external data sources using fetch, JavaScript's native HTTP client.

Why Fetch Over Other Methods

JavaScript offers multiple ways to make HTTP requests. XMLHttpRequest came first but requires verbose syntax. jQuery's $.ajax() simplified things but adds library weight. Fetch is built into modern browsers. No external dependencies. Promise-based for clean async code. Here's how they compare:

Fetch Benefits

Native browser API, Promise-based, lightweight, modern syntax

XMLHttpRequest

Callback-based, verbose setup, harder error handling

The DataFlow team needs real-time revenue data from their analytics API. Fetch makes this connection seamless.

Basic Fetch Syntax

Fetch takes a URL and returns a Promise. The response needs conversion to usable JavaScript data:
// Simple fetch request
fetch('https://api.example.com/revenue')
  .then(response => response.json())
  .then(data => {
    console.log(data); // Use the data
  });
But React components need state management. You can't just log API data — you need to store it for rendering. Here's how fetch integrates with useState:
function RevenueCard() {
  const [revenue, setRevenue] = useState(0);

  useEffect(() => {
    fetch('/api/revenue')
      .then(response => response.json())
      .then(data => setRevenue(data.total));
  }, []);

  return <h2>${revenue.toLocaleString()}</h2>;
}
DataFlow Dashboard

What just happened?

The component starts with revenue: 0 and loading: true. useEffect runs after first render, simulates an API call, then updates state. React re-renders with fresh data. Try modifying the timeout duration to see loading states.

Handling Loading States

Users hate blank screens while APIs load. Professional apps show loading indicators. Here's the pattern:
function UsersList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/users')
      .then(response => response.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading users...</div>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
DataFlow Dashboard
Notice the conditional rendering. While loading is true, show a spinner. Once data arrives, render the list. This prevents flickering empty states.

Error Handling

Networks fail. APIs go down. Servers return errors. Your React components must handle these gracefully:
function StatsCard() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/stats')
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to load stats');
        }
        return response.json();
      })
      .then(data => setData(data))
      .catch(err => setError(err.message))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>Orders: {data.orders}</div>;
}
DataFlow Dashboard

Critical Detail

Fetch doesn't reject on HTTP error status codes like 404 or 500. You must check response.ok manually. This trips up many developers coming from other HTTP libraries.

Async/Await Syntax

Promise chains with .then() get messy. Modern JavaScript prefers async/await for cleaner code:
function OrdersList() {
  const [orders, setOrders] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchOrders = async () => {
      try {
        const response = await fetch('/api/orders');
        if (!response.ok) throw new Error('Failed');
        const data = await response.json();
        setOrders(data);
      } catch (error) {
        console.error('Fetch error:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchOrders();
  }, []);

  // Render logic here
}
But there's a gotcha. You can't make useEffect itself async. That's why we create an inner async function. React expects useEffect to either return nothing or a cleanup function, not a Promise.

POST Requests and Form Data

Reading data is half the story. Most apps need to send data too. The DataFlow team wants users to create custom analytics reports:
function CreateReport() {
  const [title, setTitle] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch('/api/reports', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ title, type: 'revenue' })
      });

      if (response.ok) {
        alert('Report created!');
        setTitle('');
      }
    } catch (error) {
      alert('Failed to create report');
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Report title"
      />
      <button disabled={loading}>
        {loading ? 'Creating...' : 'Create Report'}
      </button>
    </form>
  );
}
DataFlow Dashboard
Key details for POST requests: set the HTTP method, add Content-Type headers, and stringify JSON data in the body. The button disables during submission to prevent duplicate requests.

Complete DataFlow Example

Here's how the DataFlow StatsBar component loads all four KPI metrics from an API:
function StatsBar() {
  const [stats, setStats] = useState({
    revenue: 0, users: 0, orders: 0, growth: 0
  });
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadStats = async () => {
      try {
        const response = await fetch('/api/dashboard/stats');
        const data = await response.json();
        setStats(data);
      } catch (error) {
        console.error('Stats failed:', error);
      } finally {
        setLoading(false);
      }
    };

    loadStats();
  }, []);

  const StatCard = ({ label, value, prefix = '' }) => (
    <div style={{ padding: '16px', background: '#f8fafc' }}>
      <h4>{label}</h4>
      <div>{loading ? '...' : prefix + value.toLocaleString()}</div>
    </div>
  );

  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
      <StatCard label="Revenue" value={stats.revenue} prefix="$" />
      <StatCard label="Users" value={stats.users} />
      <StatCard label="Orders" value={stats.orders} />
      <StatCard label="Growth" value={stats.growth} prefix="+" />
    </div>
  );
}
DataFlow Dashboard

What just happened?

The component loads with placeholder stats, makes an API call, then updates all four cards simultaneously. The StatCard subcomponent handles the loading state with "..." text. Try refreshing to see the loading sequence again.

Performance Tip

Making multiple API calls in separate useEffect hooks can slow your app. Consider combining related data into single endpoints. Instead of four calls for each stat, one /api/dashboard/stats call returns everything.

Modern React apps live and breathe through API connections. Master fetch patterns — loading states, error handling, and POST requests — and you can build any data-driven interface. Netflix streams millions of movies because their React components seamlessly fetch content recommendations. Your DataFlow dashboard now does the same with business metrics. The next leap comes with dedicated HTTP libraries like Axios, which add request interceptors, automatic JSON parsing, and better error handling. But fetch gives you the foundation every React developer needs.

Quiz

1. Your DataFlow component shows revenue data but breaks when the API returns a 500 error. What's the most likely issue?


2. You want to use async/await in useEffect to load DataFlow user data. What's the correct approach?


3. Your DataFlow stats cards show empty values while the API loads, confusing users. What's the best solution?


API Calls with Axios

Take your API skills to the next level with Axios — automatic JSON parsing, request interceptors, and built-in error handling that makes fetch look primitive.