Skip to main content

How to cancel a request inside useEffect()?

The problem

If a component unmounts (for example, the user left the page) while an async request (fetch, axios, setTimeout) is still running, it still completes, and you'd try to call setState on an already removed component.

The result: a warning in the console extra load on the network a memory leak


The solution - cancel the request in the useEffect cleanup function

Option 1. With AbortController (built into fetch)

javascript
import { useEffect, useState } from "react"; function UserProfile({ id }) { const [user, setUser] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Create a cancellation controller const controller = new AbortController(); const signal = controller.signal; // Run the async request async function loadUser() { try { const res = await fetch(`/api/users/${id}`, { signal }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setUser(data); } catch (err) { // Ignore the error if the request was canceled if (err.name !== "AbortError") setError(err); } } loadUser(); // Return the cleanup function - cancel the request on unmount return () => controller.abort(); }, [id]); if (error) return <p>Error: {error.message}</p>; if (!user) return <p>Loading...</p>; return <p>Name: {user.name}</p>; }

What the code does:

  • AbortController creates a signal (signal) that can be passed into fetch.
  • When controller.abort() is called:
    • fetch is aborted instantly;
    • the promise goes into catch with an { name: 'AbortError' } error;
    • we simply ignore it.

This is the official, standard way to cancel a fetch.


Option 2. For other async operations

If you're using something that doesn't support AbortController (for example, axios, setTimeout, WebSocket):

a) axios

javascript
useEffect(() => { const controller = new AbortController(); axios.get('/api/users', { signal: controller.signal }) .then(res => setUser(res.data)) .catch(err => { if (axios.isCancel(err)) return; // request canceled console.error(err); }); return () => controller.abort(); }, []);

axios 1.4+ already supports signal from AbortController, older versions use CancelToken (deprecated).

b) setTimeout / setInterval

javascript
useEffect(() => { const timeout = setTimeout(() => setCount(c => c + 1), 3000); return () => clearTimeout(timeout); }, []);

c) WebSocket or EventSource

javascript
useEffect(() => { const socket = new WebSocket("wss://example.com"); socket.onmessage = e => console.log(e.data); return () => socket.close(); }, []);

Option 3. If the library manages cancellation itself

Libraries like TanStack Query (React Query) or SWR already:

  • cancel requests on unmount;
  • do not call setState after unmount;
  • cache results and reuse them.

Example with React Query:

javascript
const { data, isLoading } = useQuery({ queryKey: ['user', id], queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()) });

Here everything is safe out of the box.


SUMMARY

What to doHow
Create a controllerconst controller = new AbortController()
Pass the signal into the request{ signal: controller.signal }
Catch the error in catchif (err.name === 'AbortError') return;
Clean up in the cleanup functionreturn () => controller.abort();

In short:

An async request inside useEffect() should be canceled in the cleanup function, to prevent updating the state of an unmounted component and to avoid memory leaks. fetch uses AbortController, other APIs have their own methods (clearTimeout, socket.close(), etc.).

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.