Back to blog

How to Fix React Warning: Cant Perform State Update on Unmounted Component

When developing React applications, you may notice a warning block in your browser console:

Warning: Can't perform a React state update on an unmounted component. 
This is a no-op, but it indicates a memory leak in your application. 
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

While React removed this console output in v18+ because it doesn't always crash modern renderers, the warning indicates a real logic flaw: an asynchronous operation resolved and attempted to update state on a component that has already been removed from the DOM.

In this guide, we will analyze why this memory leak warning happens and show you how to resolve it using cleanup functions and AbortController.

Why Does this Warning Happen?

Imagine a user loads a profile page component. The component triggers a fetch request inside a useEffect hook to retrieve user profile data.

Before the fetch request completes (e.g., due to a slow network connection), the user clicks a navigation link and leaves the page, causing React to unmount the profile page component.

When the fetch request finally resolves a few seconds later, the promise callback executes and calls setData(profile). Since the component is no longer mounted, React issues the warning to alert you that resources are still running in the background.

Solution 1: Cancel Network Requests with AbortController (Recommended)

The most robust way to resolve this warning for API calls is to abort the asynchronous request when the component unmounts. This stops the browser from wasting network bandwidth.

Modern browsers support AbortController natively. Here is how to configure it inside a React effect:

import { useState, useEffect } from 'react';

export function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<any>(null);

  useEffect(() => {
    // 1. Create an instance of AbortController
    const controller = new AbortController();
    const { signal } = controller;

    async function fetchUser() {
      try {
        // 2. Pass the controller signal to the fetch options
        const response = await fetch(`/api/users/${userId}`, { signal });
        const data = await response.json();
        
        // 3. Update state safely
        setUser(data);
      } catch (error: any) {
        if (error.name === 'AbortError') {
          console.log('Fetch successfully aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    }

    fetchUser();

    // 4. Return cleanup function to cancel request on unmount
    return () => {
      controller.abort();
    };
  }, [userId]);

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

When the user navigates away, React calls the cleanup function, triggering controller.abort(). The browser cancels the pending HTTP request, and the promise throws an AbortError which we intercept, preventing state updates.

Solution 2: Clean Up SetTimeout and Intervals

If you configure timers or polling tasks inside a component, you must cancel them in the effect cleanup function to prevent memory leaks.

import { useState, useEffect } from 'react';

export function TimerComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Start interval timer
    const intervalId = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);

    // Return cleanup to clear interval when component unmounts
    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return <div>Timer Count: {count}</div>;
}

Why You Should Avoid the isMounted Flag Hack

In older tutorials, developers bypassed the warning using a local tracking variable:

// AVOID THIS HACK
useEffect(() => {
  let isMounted = true;
  
  fetchData().then((data) => {
    if (isMounted) setData(data); // Blocks state update but does NOT stop the fetch process
  });

  return () => {
    isMounted = false;
  };
}, []);

This pattern is a code smell. While it stops the React console warning from firing, it does not stop the background network request or async callback from executing. The memory leak and network waste still occur. Always prefer aborting the request or clearing the timers directly.

Conclusion

The "Can't perform state update on unmounted component" warning is a indicator of memory leaks. To resolve it, implement cleanup return functions inside your useEffect blocks to clear pending timers, unsubscribe from event listeners, and deploy AbortController to abort ongoing HTTP requests before components are destroyed.