Introduction
When building React applications with TypeScript, fetching data from APIs is a kind of default pattern. APIs enable loosely coupled architectures, which makes your app more modular and scalable. However, handling asynchronous requests introduces a challenge:
- What happens if a component unmounts before its request completes?
- Or if you need to cancel a request manually and trigger a new one?
A practical example is modern chat applications. Take ChatGPT, for instance, you’ve probably noticed the “Stop generating” button, which immediately cancels the ongoing request so the model stops responding. This is exactly the kind of behavior we want to replicate in our own apps.
![ChatGPT]()
Without proper cancellation, you risk:
- Wasted bandwidth from unnecessary API calls
- React warns about state updates on unmounted components
- Poor user experience, as outdated requests may overwrite fresh data
The solution is the AbortController + AbortSignal API, built into modern browsers. It gives you control over request lifecycles, allowing you to abort ongoing fetches safely.
In this tutorial, we’ll walk through:
- Setting up an API call in React
- Integrating
AbortController
for cancellation
- Handling aborted requests
- Verifying cancellation in the browser’s Network tab
Step 1. Start with a Basic Fetch
First, let’s fetch some dummy data when the component mounts:
import { useEffect, useState } from "react";
export default function DataFetcher() {
const [data, setData] = useState<any>(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((res) => res.json())
.then(setData);
// No cancellation here yet
}, []);
if (!data) return <p>Loading...</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
This works fine, but there’s a catch: The request will always finish, even if you close or navigate away from the component.
- If the component unmounts before the fetch resolves, React might warn about updating state on an unmounted component.
- You also have no way to manually cancel the request if the user decides to stop it.
Step 2. Create an AbortController
In React, we usually manage cleanup inside useEffect
.
That’s where we’ll create the controller:
useEffect(() => {
const controller = new AbortController(); // Create
const { signal } = controller; // Extract the signal
fetch("https://jsonplaceholder.typicode.com/posts/1", { signal })
.then((res) => res.json())
.then(setData)
.catch((err) => {
// Catch aborts separately
if (err.name === "AbortError") { // Catch Abort
console.log("Fetch aborted!");
} else {
console.error("Fetch failed:", err);
}
});
return () => {
controller.abort(); // Cleanup on unmount
};
}, []);
Here’s what’s happening:
- Create controller:
new AbortController()
- Pass signal:
{ signal }
into fetch
- Catch cancellation: If
err.name === "AbortError"
- Abort on unmount: Return cleanup in
useEffect
Step 3. Adding a Cancel Button
In real apps, you may want the user to cancel an API request.
Here’s a full flow with Start and Cancel buttons:
import { useState } from "react";
export default function CancelableFetch() {
const [controller, setController] = useState<AbortController | null>(null);
// Stores the AbortController so we can cancel later
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState<string>("Idle");
const [data, setData] = useState<any>(null);
const startFetch = () => {
// 1: Create a new AbortController before starting the request
const newController = new AbortController();
const signal = newController.signal;
// Save it in state so we can access it later when canceling
setController(newController);
setLoading(true);
setStatus("Fetching...");
// 2: Pass the AbortSignal (signal) into fetch
fetch("https://httpbin.org/delay/5", {
signal: signal,
})
.then((res) => res.json())
.then((data) => {
setData(data);
setStatus("Success");
})
.catch((err) => {
// 3️: If the request was canceled, fetch throws an AbortError
if (err.name === "AbortError") {
setStatus("Request canceled by user");
} else {
setStatus("Error fetching data");
}
})
.finally(() => {
setLoading(false);
setController(null); // clear controller after request finishes/cancels
});
};
const cancelFetch = () => {
// 4️: Call abort() on the controller → this triggers the AbortSignal
controller?.abort();
// Reset state
setController(null);
setLoading(false);
};
return (
<div style={{ padding: "1rem", fontFamily: "sans-serif" }}>
<h2>AbortController Demo</h2>
<div style={{ marginBottom: "1rem" }}>
<button onClick={startFetch} disabled={loading} style={{ marginRight: "0.5rem" }}>
Start Fetch
</button>
<button onClick={cancelFetch} disabled={!controller}>
Cancel Fetch
</button>
</div>
<p><strong>Status:</strong> {status}</p>
{loading && <p>⏳ Waiting for response... (you can cancel)</p>}
{data && (
<pre
style={{
background: "black",
color: "lime",
padding: "1rem",
borderRadius: "8px",
maxWidth: "600px",
overflowX: "auto",
}}
>
{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
);
}
Step 4. Verify in DevTools
To actually see cancellation:
- Open your React app.
- Open DevTools > Network tab.
- Click Start Fetch. You’ll see a request in progress.
- Before it finishes, click Cancel Fetch.
- The request will show (canceled in red color with 0B in size) in the Network tab.
- Click Start Fetch again, and this time let it process. The request will process with 1.1kb in size in the Network tab.
![Demo]()
Step 5. Where Each Piece Belongs (React Flow)
- Create
AbortController
: Inside your function or useEffect
, right before you start fetch
.
- Pass
signal
: Into fetch
(or other APIs that support it).
- Catch cancellation: Inside
.catch
, check err.name === "AbortError"
.
- Abort request: Either:
- In
useEffect
cleanup (return () => controller.abort()
), or
- On a user action (like a Cancel button).
This is the proper flow in React:
create > attach > handle > cleanup.
Summary
So what do we know and what did we learn, we explore how to safely cancel API requests in React applications using TypeScript with the help of the AbortController + AbortSignal browser APIs.
- Why cancellation matters
- Prevent wasted bandwidth
- Avoid React warnings about unmounted components
- Ensure a smooth user experience (no outdated data overwriting fresh data)
- Step-by-step implementation
- 1: Basic fetch request without cancellation (the naive approach).
- 2: Using AbortController inside useEffect for cleanup when components unmount.
- 3: Adding a Start / Cancel button flow to allow manual request cancellation.
- 4: Verifying cancellation behavior using the browser’s Network tab in DevTools.
- 5: Best practices for React apps — create → attach → handle → cleanup.
Key Takeaways
- AbortController gives you control over request lifecycles.
- You can cancel requests both automatically (when a component unmounts) and manually (on user action).
- The cancellation flow in React is simple and predictable: create controller > pass signal > handle abort > cleanup..