Skip to main content

Як скасувати запит усередині useEffect()?

Проблема

Якщо компонент розмонтується (наприклад, користувач пішов зі сторінки), а асинхронний запит (fetch, axios, setTimeout) ще виконується, він все одно завершиться, і ти спробуєш викликати setState на вже видаленому компоненті.

Результат: попередження в консолі зайве навантаження на мережу витік пам'яті


Рішення - скасування запиту в cleanup-функції useEffect

Варіант 1. З AbortController (вбудовано в fetch)

javascript
import { useEffect, useState } from "react"; function UserProfile({ id }) { const [user, setUser] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Створюємо контролер скасування const controller = new AbortController(); const signal = controller.signal; // Виконуємо асинхронний запит 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) { // Ігноруємо помилку, якщо запит було скасовано if (err.name !== "AbortError") setError(err); } } loadUser(); // Повертаємо cleanup-функцію - скасовуємо запит при розмонтуванні return () => controller.abort(); }, [id]); if (error) return <p>Помилка: {error.message}</p>; if (!user) return <p>Завантаження...</p>; return <p>Ім'я: {user.name}</p>; }

Що робить код:

  • AbortController створює сигнал (signal), який можна передати в fetch.
  • При виклику controller.abort():
    • fetch миттєво переривається;
    • проміс переходить у catch з помилкою { name: 'AbortError' };
    • ми просто ігноруємо її.

Це офіційний, стандартний спосіб скасувати fetch.


Варіант 2. Для інших асинхронних операцій

Якщо ти використовуєш щось, що не підтримує AbortController (наприклад, 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; // запит скасовано console.error(err); }); return () => controller.abort(); }, []);

axios 1.4+ уже підтримує signal з AbortController, старі версії використовують CancelToken (застарілий).

b) setTimeout / setInterval

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

c) WebSocket або EventSource

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

Варіант 3. Якщо бібліотека сама керує скасуванням

Бібліотеки на кшталт TanStack Query (React Query) або SWR уже:

  • скасовують запити при розмонтуванні;
  • не викликають setState після unmount;
  • кешують результати і переюзають їх.

Приклад з React Query:

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

Тут усе безпечно з коробки.


ПІДСУМОК

Що робимоЯк
Створюємо контролерconst controller = new AbortController()
Передаємо сигнал у запит{ signal: controller.signal }
Ловимо помилку в catchif (err.name === 'AbortError') return;
Очищаємо в cleanupreturn () => controller.abort();

Коротко:

Асинхронний запит усередині useEffect() потрібно скасовувати в cleanup, щоб запобігти оновленню стану розмонтованого компонента і уникнути витоків пам'яті. Для fetch використовується AbortController, для інших API - свої методи (clearTimeout, socket.close() тощо).

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.