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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs