If you’ve ever built a React app that fetches data—like a search feature or a dashboard—you’ve probably run into this: a user clicks away mid-request, and your app still tries to process that outdated API call. Not only is this wasteful, but it can also lead to bugs like memory leaks or updating the UI with stale data. Enter AbortController, a native JavaScript API that lets you cancel those operations with ease. Let’s explore how to use it in React with some simple examples.

What Is AbortController?

AbortController is a built-in JavaScript object that gives you a way to cancel asynchronous tasks, like fetch requests. It’s been around since the DOM Standard got it in 2017, and it’s supported in all modern browsers. In React, it’s especially handy for managing side effects—like API calls in useEffect—to keep your app lean and bug-free.

Why Use It in React?

Consider a search feature where your app fetches product listings as users type. When someone types quickly, multiple requests fire in rapid succession. Without proper request cancellation, your app might display results from outdated queries rather than the most recent one. Even more problematic—if a user navigates away while requests are pending, React could attempt to update state on an unmounted component, causing errors. AbortController elegantly solves these issues by allowing you to cancel unnecessary requests on demand.

Example 1: Basic AbortController with Fetch

Let’s start with a simple fetch request in a React component. We’ll use AbortController to cancel it when the component unmounts.

import { useEffect, useState } from "react";

function Posts() {
  const [posts, setPosts] = useState(null);

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

    fetch("https://jsonplaceholder.typicode.com/posts", { signal })
      .then((response) => response.json())
      .then((data) => setPosts(data))
      .catch((error) => {
        if (error.name === "AbortError") {
          console.log("Request canceled!");
        } else {
          console.error("Fetch error:", error);
        }
      });

    return () => controller.abort(); // Cleanup on unmount
  }, []);

  return (
    <div className="product-list">
      <h2>Posts</h2>
      {!posts ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {posts.slice(0, 3).map((post) => (
            <li key={post.id}>{post.title}</li>
          ))}
          <li>{posts.length} total posts loaded</li>
        </ul>
      )}
    </div>
  );
}

export default Posts;

How It Works

  1. Create an AbortController and pass its signal to fetch.
  2. Fetch retrieves posts from the public API.
  3. When the component unmounts (e.g., user navigates away), the cleanup function calls controller.abort(), canceling the request and logging “Request canceled!” to the console.
  4. The UI displays “Loading…” while fetching, then shows the first three post titles and total count once loaded.

Example 2: Canceling on User Action

Now let’s make it interactive. Imagine a search bar where we fetch results as the user types, but we cancel old requests when a new one starts.

import { useEffect, useState } from "react";

function SearchBar() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }

    const controller = new AbortController();
    const signal = controller.signal;

    fetch(`https://api.example.com/search?q=${query}`, { signal })
      .then((response) => response.json())
      .then((data) => setResults(data))
      .catch((error) => {
        if (error.name !== "AbortError") {
          console.error("Search error:", error);
        }
      });

    return () => controller.abort();
  }, [query]); // Runs every time query changes

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map((item, index) => (
          <li key={index}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchBar;

How It Works

  1. The useEffect hook triggers whenever the query changes as the user types.
  2. The cleanup function automatically cancels any pending request before starting a new fetch, ensuring only results from the latest query update the UI.
  3. This prevents stale data by canceling outdated requests—you can verify this by typing quickly and observing canceled requests in your browser’s Network tab.

Example 3: Aborting Event Listeners with AbortController

Example 3: Aborting Event Listeners with AbortController Beyond fetch requests, AbortController can also manage event listeners. Let’s create a component that listens for window resize events and stops listening when the component unmounts or when we explicitly abort it.

import { useEffect, useState } from "react";

function ResizeTracker() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

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

    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    // Add event listener with AbortController signal
    window.addEventListener("resize", handleResize, { signal });

    return () => {
      console.log("Aborting resize event listener...");
      controller.abort(); // Removes the event listener
    };
  }, []);

  return (
    <div>
      <h2>Window Size</h2>
      <p>Width: {windowSize.width}px</p>
      <p>Height: {windowSize.height}px</p>
    </div>
  );
}

export default ResizeTracker;

How It Works

  1. We create an AbortController and use its signal in the addEventListener options.
  2. The resize event updates the state with the current window dimensions.
  3. When the component unmounts, the cleanup function calls controller.abort(), which automatically removes the event listener, preventing memory leaks.
  4. The UI displays the current window width and height, updating in real-time as you resize the window.

Tips

  • Error Handling: Always check for AbortError in your .catch block so you don’t treat cancellations as failures.
  • Beyond Fetch: You can use AbortController with other APIs that support signals, like setTimeout (via libraries) or custom promises.

In Conclusion

AbortController is a small but mighty tool for React developers. It keeps your app performant by canceling unnecessary requests and prevents those pesky “Can’t set state on an unmounted component” errors. Whether you’re fetching data once or handling rapid user input, it’s a clean way to stay in control of your async operations.