Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як скасувати запит усередині useEffect()?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Якщо компонент розмонтовується, поки асинхронний запит ще виконується, він **все одно завершиться**, і ти спробуєш викликати `setState` на вже видаленому компоненті - тому запит потрібно скасовувати в cleanup-функції `useEffect`. **Ключове:** для `fetch` використовується `AbortController`, для інших API - свої методи (`clearTimeout`, `socket.close()` тощо).Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Проблема Якщо компонент розмонтується (наприклад, користувач пішов зі сторінки), а асинхронний запит (`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 }` | | Ловимо помилку в `catch` | `if (err.name === 'AbortError') return;` | | Очищаємо в cleanup | `return () => controller.abort();` | --- ### Коротко: > Асинхронний запит усередині `useEffect()` потрібно скасовувати в cleanup, > щоб запобігти оновленню стану розмонтованого компонента > і уникнути витоків пам'яті. > Для `fetch` використовується `AbortController`, для інших API - свої методи (`clearTimeout`, `socket.close()` тощо).Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.