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; useawait
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
- Load 10 posts and add a dropdown to choose limit.
- Show a retry button when there is an error.
Checks for Understanding
- How do you cancel an in-flight fetch and why would you do it?
- Why should you check res.ok before using the response body?
- What strategies help avoid setState on an unmounted component?
Answers
- Use AbortController and pass its signal to fetch; cancel to prevent race conditions and to avoid updating with stale data.
- Non-OK responses (4xx/5xx) won’t throw by default; checking res.ok lets you create a meaningful error path and show UI errors.
- Abort/cancel on unmount, keep a canceled flag, or guard with isMounted refs; libraries often abstract this for you.