JAVASCRIPT
Managing Async Operations with `useEffect` Cleanup for Data Fetching
Implement robust data fetching in React components using `useEffect` with proper cleanup to prevent memory leaks and handle unmounted component updates.
import React, { useState, useEffect } from 'react';
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Flag to track if the component is mounted
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (isMounted) { // Only update state if the component is still mounted
setData(result);
}
} catch (e) {
if (isMounted) {
setError(e);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
// Cleanup function: runs when the component unmounts or before re-running the effect
return () => {
isMounted = false; // Set flag to false on unmount
};
}, [userId]); // Re-run effect if userId changes
if (loading) return <div>Loading user data...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No data found.</div>;
return (
<div style={{ border: '1px solid #ccc', padding: '15px', margin: '10px' }}>
<h2>User Details</h2>
<p><strong>Name:</strong> {data.name}</p>
<p><strong>Email:</strong> {data.email}</p>
<p><strong>Phone:</strong> {data.phone}</p>
</div>
);
}
// Example parent component to demonstrate unmounting
function App() {
const [showUser, setShowUser] = useState(true);
const [currentUserId, setCurrentUserId] = useState(1);
return (
<div>
<button onClick={() => setShowUser(!showUser)}>
{showUser ? 'Hide User 1' : 'Show User 1'}
</button>
<button onClick={() => setCurrentUserId(currentUserId === 1 ? 2 : 1)}>
Switch User ({currentUserId === 1 ? 'Current: 1, Next: 2' : 'Current: 2, Next: 1'})
</button>
{showUser && <DataFetcher userId={currentUserId} />}
</div>
);
}
export default App;
How it works: This snippet demonstrates robust asynchronous data fetching using `useEffect`. It includes state for loading and errors, and crucially, a cleanup function that sets an `isMounted` flag. This flag prevents state updates on an unmounted component, a common source of memory leaks and warnings in React. The effect re-runs whenever `userId` changes, ensuring fresh data, while the cleanup phase guards against race conditions and stale closures when the component quickly mounts/unmounts or its dependencies change.