React - Calling APIs & Actions

Overview

Estimated time: 20–30 minutes

Call APIs based on user actions, show loading and error states, and cancel in-flight requests to keep the UI responsive.

Try it: Load Posts on Button Click

View source
const { useState, useRef } = React;
function Posts(){
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const controllerRef = useRef(null);
  async function load(){
    try {
      setError(''); setLoading(true);
      if (controllerRef.current) controllerRef.current.abort();
      controllerRef.current = new AbortController();
      const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5', { signal: controllerRef.current.signal });
      if (!res.ok) throw new Error('Network error');
      const data = await res.json();
      setPosts(data);
    } catch (e) {
      if (e.name !== 'AbortError') setError(e.message || 'Error');
    } finally {
      setLoading(false);
    }
  }
  function cancel(){ if (controllerRef.current) controllerRef.current.abort(); }
  return (
    <div>
      <button onClick={load} disabled={loading}>{loading ? 'Loading...' : 'Load Posts'}</button>
      <button onClick={cancel} style={{marginLeft:8}} disabled={!loading}>Cancel</button>
      {error && <div style={{color:'salmon', marginTop:8}}>{error}</div>}
      <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-api')).render(<Posts />);

Syntax primer

  • fetch(url) returns a Promise; use await for readability.
  • AbortController cancels in-flight requests to avoid race conditions.

Common pitfalls

  • Forgetting to handle non-OK responses (res.ok).
  • Updating state after unmount; cancel or guard against setState on unmounted components.

Exercises

  1. Load 10 posts and add a dropdown to choose limit.
  2. Show a retry button when there is an error.

Checks for Understanding

  1. How do you cancel an in-flight fetch and why would you do it?
  2. Why should you check res.ok before using the response body?
  3. What strategies help avoid setState on an unmounted component?
Answers
  1. Use AbortController and pass its signal to fetch; cancel to prevent race conditions and to avoid updating with stale data.
  2. Non-OK responses (4xx/5xx) won’t throw by default; checking res.ok lets you create a meaningful error path and show UI errors.
  3. Abort/cancel on unmount, keep a canceled flag, or guard with isMounted refs; libraries often abstract this for you.