Explore Drafts

Why I never used AbortController in my React apps — and still don’t need to

dpi-why-i-never-needed-abort-controller-in-react-apps

You've probably seen developers reaching for AbortController in React apps to cancel fetch requests and "prevent memory leaks." It's becoming a common suggestion — but do you actually need it?

I've seen a growing number of devs recommending AbortController to manage async behavior in React — especially for canceling fetch requests inside useEffect. It's popping up in tutorials, Twitter threads, and even official docs.

Here's the twist:
I've never used AbortController in any React project.
And I've never had a reason to regret it.

In this post, I'm going to walk you through why I deliberately don't use AbortController, what happens internally in React when an async effect cleans up, and the patterns I use instead that are simpler, clearer, and often more robust.

Understanding the problem: async calls and cleanup in React

Here's the classic scenario people worry about:

useEffect(() => {
  fetch("/api/data")
    .then(res => res.json())
    .then(data => setData(data));
}, []);

This works… until the component unmounts before the fetch completes.

In older React versions (pre-18), this would throw a warning:

⚠️ "Can't perform a React state update on an unmounted component."

That's where AbortController came into fashion — it gives you a way to cancel the fetch request on cleanup, theoretically avoiding the issue.

But here's what changed…

React 18 started ignoring state updates automatically

React 18 introduced a subtle but powerful internal change:
It silently ignores state updates made to unmounted components.

So in this setup:

.then(data => setData(data))

Even if the component unmounts before this runs, React will just drop the update on the floor. No error. No warning. No crash. No need to manually track if the component is still mounted.

That alone eliminates most use cases for AbortController in UI logic.

What React does under the hood

React uses a structure called the Fiber tree to track component instances. Every time a component renders, React creates or updates its corresponding Fiber node.

When a component unmounts:

  • Its Fiber node is deleted.
  • Any state updates targeting that Fiber are discarded.

In ReactFiberCommitWork.js, the update queue system checks if a component still has a valid alternate node. If not, it exits early:

if (finishedWork.alternate === null) {
  // Component unmounted — ignore the update
  return;
}

In short: if React sees that the component is gone, it simply does nothing.

This is one of the most under-appreciated upgrades in React 18, and it's why you don't need to "defend" against unmounted state updates anymore.

What AbortController actually does

AbortController lets you manually cancel an in-progress fetch() call:

useEffect(() => {
  const controller = new AbortController();

  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(data => setData(data))
    .catch(err => {
      if (err.name !== "AbortError") {
        console.error("Actual error:", err);
      }
    });

  return () => controller.abort();
}, []);

It's useful when:

  • You want to stop a request from continuing
  • You're making multiple fetches in a loop or with dependencies
  • You're integrating with third-party APIs that limit requests

But here's the problem:

React doesn't care whether the fetch completes or aborts — it just ignores state updates if the component is gone.

So unless you're doing something outside of UI state (like tracking memory, logging, etc.), AbortController is usually solving a problem that no longer exists.

The case against AbortController in React apps

Adds complexity

You now have to deal with AbortController, AbortError, signal, and .catch() even if you don't need to.

Doesn't integrate with async/await cleanly

You have to manually check err.name ==="AbortError" — that clutters your logic.

Doesn't solve UI bugs in React 18+

Because React already discards state updates, you're not preventing anything meaningful in most apps.

Encourages premature optimization

99% of frontend fetches are lightweight. Unless you're doing huge streaming payloads or infinite polling, the "benefit" of aborting is minimal.

What I use instead: Listener patterns and cleanup flags

If I ever do need to prevent unnecessary fetches or avoid updates in flight, I go with this approach:

useEffect(() => {
  let shouldSetData = true;

  fetch("/api/data")
    .then(res => res.json())
    .then(data => {
      if (shouldSetData) {
        setData(data);
      }
    });

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

It's readable, predictable, and works for any kind of async task — not just fetch.

Better yet, I sometimes combine this with event listeners. For example, don't fetch data if the user switched tabs:

useEffect(() => {
  let active = true;

  const onVisibilityChange = () => {
    if (document.hidden) active = false;
  };

  document.addEventListener("visibilitychange", onVisibilityChange);

  fetch("/api/data")
    .then(res => res.json())
    .then(data => {
      if (active) setData(data);
    });

  return () => {
    active = false;
    document.removeEventListener("visibilitychange", onVisibilityChange);
  };
}, []);

I'm in control — and I didn't need AbortController at all.

When AbortController might actually be useful

There are a few legit cases:

  • You're dealing with file uploads, video streaming, or large downloads
  • You want to track request cancellation in logs or metrics
  • You're building a custom hook used across many components
  • You're doing preloading, prefetching, or other background tasks where request cost matters

But if your only concern is "I don't want to update state after unmount" — React is already handling that.

Conclusion: You don't need AbortController for UI fetches in React

I've never used AbortController in a React project — and after diving deep into React 18's internals, I feel even more confident in that choice.

React is smarter now. It doesn't update unmounted components.
It handles state cleanup.
It gives me all the guardrails I need — and when I want more control, I reach for cleanup flags or event listeners, not low-level browser APIs.

So before you reach for AbortController, ask yourself:
Do I actually need to cancel this request — or am I just trying to avoid an outdated warning?

In most cases, the cleanest code is the one that trusts the runtime.

AbortController might seem like the "safe" way to handle async effects in React, but in most real-world UI scenarios, it's just adding extra complexity without solving a real problem. React 18 already protects you from state updates on unmounted components. That alone removes the main reason people used AbortController in the first place.

Instead of managing low-level cancellations, rely on cleanup flags, visibility listeners, and trusting React's internal mechanisms. It keeps code simpler, more declarative, and just as safe — if not safer.

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.