Fetching from an API
Overview
The most common use case for `useEffect` is fetching data from a backend server. Because fetching is asynchronous (it takes time over the network) and occurs outside of React's pure rendering, it must be done inside an effect. Standard practice involves using the browser's native `fetch` API or a library like Axios.
Syntax
This is the classic way to fetch data. The component mounts, renders an empty list, the effect fires in the background, grabs the data, updates the state, and triggers a re-render to show the posts.
function PostList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
// 1. Start fetch
fetch("https://jsonplaceholder.typicode.com/posts")
// 2. Parse JSON
.then(response => response.json())
// 3. Save to state
.then(data => setPosts(data))
// 4. Catch errors
.catch(error => console.error("Error:", error));
}, []); // Empty array ensures we only fetch once on mount
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}`async/await` is often preferred for readability, but because `useEffect` must return either nothing or a cleanup function (not a Promise), you have to wrap the logic in a standard function first.
useEffect(() => {
// You cannot make the useEffect callback itself async.
// Instead, define an async function inside it, and call it immediately.
const loadPosts = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
setPosts(data);
} catch (error) {
console.error(error);
}
};
loadPosts();
}, []);Common Pitfalls
- Forgetting the dependency array entirely, which causes an infinite loop: Fetch -> Set State -> Re-render -> Fetch -> Set State...
Interview Tips
- In modern React (React 18+), raw `useEffect` fetching is discouraged for production apps. Interviewers will want you to mention libraries like TanStack Query (React Query) or SWR for caching, deduping, and background refetching.
Real-World Example
Canceling a fetch request if the component unmounts before the request finishes (preventing 'state update on unmounted component' warnings).
useEffect(() => {
let isMounted = true; // Flag to track mount status
fetch('/api/data')
.then(res => res.json())
.then(data => {
// Only update state if the user hasn't navigated away
if (isMounted) setData(data);
});
return () => {
isMounted = false; // Cleanup flips the flag
};
}, []);