![]()
Not Canceling API Requests on Unmount Leading to Memory Leaks
When working with API calls in React, especially inside useEffect, failing to cancel ongoing requests when the component unmounts can lead to memory leaks. This is particularly problematic when the API response updates the state after the component is no longer in the DOM.
Common Mistake Example (Missing Cleanup):
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/user/${userId}`)
.then(res => res.json())
.then(data => setUser(data)); // ❌ No cleanup, potential memory leak
// No cleanup function provided
}, [userId]);
return <h1>{user ? user.name : 'Loading...'}</h1>;
}
Why This Causes a Memory Leak:
- If the component unmounts before the API call completes, React tries to update the state of an unmounted component.
- This can cause warnings in React and lead to unnecessary memory usage.
✅ Correct Way (Using AbortController for Cleanup):
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController(); // ✅ Create an AbortController
const signal = controller.signal;
fetch(`https://api.example.com/user/${userId}`, { signal })
.then(res => res.json())
.then(data => setUser(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Fetch aborted'); // ✅ Handle abort error gracefully
} else {
console.error(err);
}
});
return () => {
controller.abort(); // ✅ Cleanup: cancel request on unmount
};
}, [userId]);
return <h1>{user ? user.name : 'Loading...'}</h1>;
}
Why This Works:
AbortControllerallows you to cancel the request when the component unmounts.- The cleanup function inside
useEffectensures the API request is canceled, preventing memory leaks.
Common Mistake: Not Handling Errors Correctly
import { useEffect, useState } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('https://api.example.com/posts')
.then(res => res.json())
.then(data => setPosts(data))
.catch(err => console.error(err)); // ❌ No handling for aborted fetch
}, []);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Fix:
- Always handle the
AbortErrorto prevent unnecessary error logs. - Use
AbortControllerfor proper cleanup.
✅ Using AbortController with Async/Await
import { useEffect, useState } from 'react';
function Comments({ postId }) {
const [comments, setComments] = useState([]);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchComments = async () => {
try {
const response = await fetch(`https://api.example.com/comments/${postId}`, { signal });
const data = await response.json();
setComments(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Request was aborted');
} else {
console.error(err);
}
}
};
fetchComments();
return () => {
controller.abort(); // ✅ Cancel the request on unmount
};
}, [postId]);
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
Why This Works:
- The async function handles both data fetching and error handling.
- The request is aborted if the component unmounts, preventing memory leaks.
Mistake: Forgetting to Cleanup with Third-Party Libraries
If you’re using libraries like axios, they don’t support AbortController directly. In such cases, use the CancelToken:
import axios from 'axios';
import { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
const cancelTokenSource = axios.CancelToken.source();
axios.get('https://api.example.com/data', {
cancelToken: cancelTokenSource.token,
})
.then(response => setData(response.data))
.catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
console.error(error);
}
});
return () => {
cancelTokenSource.cancel('Operation canceled by the user.'); // ✅ Cleanup
};
}, []);
return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}
Key Takeaways:
- Always clean up API calls in
useEffectusingAbortControllerorCancelToken. - Handle errors gracefully, especially for canceled requests.
- This improves performance, prevents memory leaks, and avoids React warnings.
