Skip to content

React Tutorial Data and APIs

Aborting Fetch Requests in React with AbortController

Lesson 98 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

Sometimes a request is no longer needed: the user typed another letter, navigated away or closed the dialog. You can cancel it so it stops using bandwidth and cannot update state later.

AbortController in two lines

script.js
JS
const controller = new AbortController();
fetch(url, { signal: controller.signal });   // give fetch the signal
controller.abort();                           // later: cancel the request

When you abort, the fetch promise rejects with an error whose name is "AbortError". This is not a failure to report to the user; it is what you asked for. Read more in Abort Fetch.

Abort in an effect cleanup

The cleanup function is the natural place. It runs when the dependencies change or the component unmounts, which is exactly when the old request becomes unwanted:

App.jsx
JSX
function slowApi(text, signal) {
  // A pretend request that honours the AbortSignal, like fetch does
  return new Promise((resolve, reject) => {
    const id = setTimeout(() => resolve("Loaded: " + text), 500);
    signal.addEventListener("abort", () => {
      clearTimeout(id);
      reject(new DOMException("Aborted", "AbortError"));
    });
  });
}

function App() {
  const [query, setQuery] = React.useState("");
  const [result, setResult] = React.useState("");
  const [aborted, setAborted] = React.useState(0);

  React.useEffect(() => {
    if (!query) return;
    const controller = new AbortController();

    slowApi(query, controller.signal)
      .then(setResult)
      .catch((err) => {
        if (err.name === "AbortError") { setAborted((n) => n + 1); return; }  // expected: ignore
        setResult("Error: " + err.message);
      });

    return () => controller.abort();   // cancel the previous request
  }, [query]);

  return (
    <div>
      <label>Search <input aria-label="Search" value={query} onChange={(e) => setQuery(e.target.value)} /></label>
      <p>{result}</p>
      <small>Requests cancelled: {aborted}</small>
    </div>
  );
}

Type quickly: each new letter cancels the previous request, and the counter goes up. Only the final request completes. (In a real project the first argument of fetch replaces slowApi: fetch(url, { signal: controller.signal }).)

With a real fetch

App.jsx
JSX
const url = "data:application/json,[1,2,3]";

function App() {
  const [text, setText] = React.useState("Loading...");

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

    async function load() {
      try {
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error("HTTP " + response.status);
        const data = await response.json();
        setText("Got " + data.length + " items");
      } catch (err) {
        if (err.name !== "AbortError") setText("Failed: " + err.message);
      }
    }

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

  return (
    <div>
      <p>Data</p>
      <p>{text}</p>
    </div>
  );
}

Remember: the AbortError branch must not set an error state. Otherwise a normal cancel looks like a failure.

Timeouts

AbortSignal.timeout(ms) creates a signal that aborts after a delay, with no controller:

script.js
JS
const response = await fetch(url, { signal: AbortSignal.timeout(8000) });

On timeout the error name is "TimeoutError". To combine a timeout with your own cancel signal, use AbortSignal.any([controller.signal, AbortSignal.timeout(8000)]) in browsers that support it.

Strict Mode in development

In development, Strict Mode runs effects twice. With the pattern above you will see the first request aborted straight away and the second one finish. That is correct behaviour. In the Network tab the first request shows as cancelled.

Abort vs ignore flag

TechniqueStops the network requestPrevents the late state update
ignore flag in cleanupNo (the request still finishes)Yes
AbortControllerYes (browser cancels it)Yes, because the promise rejects

Aborting saves bandwidth and server work; the ignore flag is enough for small data. Aborting does not cancel what the server has already started, so do not rely on it for actions that must not happen (such as payments).

Cancelling uploads and other things

AbortController also works with other APIs that accept a signal: event listeners (addEventListener(..., { signal })), streams and many libraries (Axios and TanStack Query support it).

Key points

  • Create an AbortController in the effect, pass its signal to fetch, and call abort() in cleanup.
  • Treat AbortError as expected: do not show it as an error.
  • AbortSignal.timeout(ms) adds a timeout.