Як скасувати запит усередині useEffect()?
Проблема
Якщо компонент розмонтується (наприклад, користувач пішов зі сторінки),
а асинхронний запит (fetch, axios, setTimeout) ще виконується,
він все одно завершиться, і ти спробуєш викликати setState на вже видаленому компоненті.
Результат: попередження в консолі зайве навантаження на мережу витік пам'яті
Рішення - скасування запиту в cleanup-функції useEffect
Варіант 1. З AbortController (вбудовано в fetch)
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
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
useEffect(() => {
const timeout = setTimeout(() => setCount(c => c + 1), 3000);
return () => clearTimeout(timeout);
}, []);c) WebSocket або EventSource
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:
const { data, isLoading } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then(r => r.json())
});Тут усе безпечно з коробки.
ПІДСУМОК
| Що робимо | Як |
|---|---|
| Створюємо контролер | const controller = new AbortController() |
| Передаємо сигнал у запит | { signal: controller.signal } |
Ловимо помилку в catch | if (err.name === 'AbortError') return; |
| Очищаємо в cleanup | return () => controller.abort(); |
Коротко:
Асинхронний запит усередині
useEffect()потрібно скасовувати в cleanup, щоб запобігти оновленню стану розмонтованого компонента і уникнути витоків пам'яті. ДляfetchвикористовуєтьсяAbortController, для інших API - свої методи (clearTimeout,socket.close()тощо).
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.