React API Integration using useEffect β Fetch Data Like a Pro (2025 Guide)
Introduction β Why Use useEffect for API Calls?
In React.js, useEffect is the go-to hook for running side effects like API calls. It helps ensure that data fetching:
- Happens after the component renders
- Re-runs only when dependencies change
- Cleans up properly on unmount
Using useEffect for API integration keeps your code predictable, maintainable, and reactive to UI changes.
In this guide, youβll learn:
- How to fetch data using
useEffect - Manage loading, error, and success states
- Use async/await patterns
- Clean up API calls with
AbortController
1. Basic GET Request with useEffect
import { useEffect, useState } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
})
.then((data) => setPosts(data))
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading posts...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{posts.map((post) => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
Fetches posts on first render
Handles loading and error states
2. Using async/await Inside useEffect
Reactβs useEffect doesnβt support async directly, but you can wrap it in an Immediately Invoked Function Expression (IIFE).
useEffect(() => {
(async () => {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Server Error');
const json = await res.json();
setData(json);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
})();
}, []);
Use try/catch/finally for safe async handling
3. Cleanup API Calls with AbortController
Cancel fetch calls if the component unmounts before the request completes.
useEffect(() => {
const controller = new AbortController();
fetch('/api/items', { signal: controller.signal })
.then(res => res.json())
.then(setItems)
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort(); // Cleanup
}, []);
Prevents memory leaks and invalid state updates
Important in slow or paginated requests
4. Trigger API Call on Dependency Change
useEffect(() => {
if (!userId) return;
setLoading(true);
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
Re-fetches user data only when userId changes
5. Refactor into a Custom Hook
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then((res) => res.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') setError(err);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
Usage:
const { data, loading, error } = useFetch('/api/products');
Improves reusability across components
Best Practices
Use useEffect only for side effects
Manage loading/error states to improve UX
Cleanup long requests with AbortController
Avoid fetching on every renderβuse dependencies wisely
Abstract fetch logic into reusable hooks for scaling
Summary β Recap & Next Steps
Reactβs useEffect is perfect for integrating with APIs. With correct patterns for async/await, cleanup logic, and dependency control, you can build reliable and responsive UIs backed by real-time data.
Key Takeaways:
- Use
useEffectto call APIs on mount or dependency change - Wrap async logic in an IIFE for cleaner code
- Handle loading and error states gracefully
- Cancel requests with
AbortControllerto prevent leaks - Extract logic into reusable
useFetchhooks
Real-World Relevance:
Used for product listings, user profiles, dashboards, and real-time data in apps like Airbnb, Notion, and LinkedIn.
FAQ Section
Can useEffect be async directly?
No. Wrap it in an IIFE or call an async function from inside it.
Why use AbortController in fetch?
To cancel the request when a component unmounts or before it re-runs.
How do I prevent unnecessary API calls in useEffect?
Add proper dependency arrays and conditional checks inside the effect.
Should I use Axios inside useEffect?
Yes. Just like fetch, you can call Axios inside useEffect:
axios.get('/api/data').then(res => setData(res.data));
Can I use useEffect for POST requests too?
Yes, though POSTs are usually triggered by user actions (like form submissions), not initial renders.
Share Now :
