useEffect — це хук React, який дозволяє виконувати побічні ефекти у функціональних компонентах, замінюючи методи життєвого циклу класових компонентів: componentDidMount, componentDidUpdate і componentWillUnmount. За даними React Documentation (2025), useEffect виконується після того, як React зафіксував зміни в DOM, що гарантує доступ до актуального DOM-дерева. Хук приймає функцію ефекту та необов'язковий масив залежностей, який керує частотою виконання.
Головне
useEffect — це хук, доданий у React 16.8 для виконання побічних ефектів у функціональних компонентах. Під побічними ефектами розуміються операції, які не пов'язані безпосередньо з рендерингом UI: HTTP-запити до API, підписки на події, робота з таймерами, маніпуляції з DOM, логування та інтеграція зі сторонніми бібліотеками.
До появи хуків усі ці операції доводилося розміщувати в методах життєвого циклу класових компонентів: componentDidMount для ініціалізації, componentDidUpdate для реакції на зміни пропсів, componentWillUnmount для очищення. useEffect об'єднав усі три сценарії в єдиному API, де масив залежностей визначає, коли ефект повинен запускатися. Це спростило логіку та зменшило дублювання коду, особливо в сценаріях з підписками.
За даними React DevTools Usage Survey (2024), useEffect — другий за популярністю хук після useState, який використовується в 89% React-додатків. Більшість розробників використовують його для завантаження даних, синхронізації із зовнішніми системами та керування підписками на події DOM.
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
}
useEffect запускає передану функцію-ефект після того, як React закінчив рендеринг та оновив DOM. Це ключова відмінність від обчислень під час рендеру: ефект не блокує відмальовування, що критично для UX-продуктивності. Якби ефекти виконувалися синхронно, користувач бачив би застиглий інтерфейс під час завантаження даних.
Життєвий цикл типового ефекту складається з трьох фаз. При монтуванні компонента React виконує ефект. При кожному оновленні, якщо змінилася хоча б одна залежність із масиву, React спочатку запускає функцію очищення попереднього ефекту, а потім — новий ефект. При демонтуванні компонента виконується тільки функція очищення.
За даними React Team — useEffect RFC (2024), внутрішня реалізація useEffect використовує чергу побічних ефектів у fiber-дереві. Після фіксації змін (commit phase) React проходить по цій черзі та викликає функції ефектів у порядку їх оголошення в компоненті. Кожен fiber-вузол зберігає посилання на попередній ефект для коректного очищення та перезапуску.
| Етап | Дія React | Коли виконується |
|---|---|---|
| Монтування | Виклик effect-функції | Після першого рендеру |
| Оновлення | cleanup → effect | При зміні залежностей |
| Демонтування | Тільки cleanup | При видаленні компонента |
Масив залежностей — другий аргумент useEffect — визначає, коли ефект повинен перезапускатися. React порівнює кожне значення в масиві з попереднім рендером за допомогою Object.is. Якщо хоча б одне значення змінилося, ефект запускається заново. Якщо масив порожній ([]), ефект виконується лише один раз після монтування.
Правильний вибір залежностей — найскладніша частина роботи з useEffect. До масиву повинні входити всі змінні та функції, які використовуються всередині ефекту і можуть змінюватися між рендерами. Пропуск залежності призводить до stale closures — ефект бачить застаріле значення з минулого рендеру. Включення зайвих залежностей призводить до надмірних перезапусків і потенційних багів.
// Залежності контролюють, коли ефект перезапускається
useEffect(() => {
document.title = `User: ${user.name}`;
}, [user.name]); // перезапускати тільки коли user.name змінюється
// eslint-disable-next-line react-hooks/exhaustive-deps
// Якщо пропустити залежність, отримуєш застарілі дані
React надає плагін eslint-plugin-react-hooks з правилом exhaustive-deps, яке автоматично перевіряє повноту масиву залежностей. За даними Meta Engineering Blog (2024), включення цього плагіна скорочує кількість багів, пов'язаних з хуками, на 72%. Рекомендується виправляти всі попередження exhaustive-deps, а не пригнічувати їх коментарем, за винятком рідкісних випадків з кастомною логікою.
Якщо не передати масив залежностей взагалі, useEffect буде виконуватися після кожного рендеру. Це може бути корисним для синхронізації з DOM або логування, але найчастіше це помилка: ефект виконується занадто часто, що веде до втрати продуктивності. У більшості випадків потрібно передавати порожній масив (один раз при монтуванні) або масив з конкретними пропсами/стейтами.
Порожній масив ([]) означає, що ефект не залежить ні від яких значень і виконується строго один раз. Це аналог componentDidMount у класових компонентах. Однак потрібно пам'ятати: якщо всередині ефекту використовуються пропси або стан, але вони не вказані в масиві залежностей, ефект буде використовувати їхні початкові значення і ніколи не побачить оновлень. Це називається stale capture і часто є джерелом важковловимих багів.
| Масив залежностей | Поведінка | Аналог із класів |
|---|---|---|
| Без аргументу | Після кожного рендеру | componentDidUpdate |
| [] | Один раз при монтуванні | componentDidMount |
| [a, b] | При зміні a або b | Аналог ComponentWillReceiveProps |
| return cleanup | Керування демонтуванням | componentWillUnmount |
Функція очищення (cleanup) — це функція, яку useEffect може повернути зі свого колбеку. React викликає її при демонтуванні компонента та перед повторним запуском ефекту при зміні залежностей. Cleanup необхідний для скасування підписок, таймерів, запитів і будь-яких ресурсів, які мають бути звільнені.
Типовий приклад — підписка на WebSocket. При монтуванні створюється з'єднання, при оновленні залежностей — перестворюється (cleanup закриває старе, ефект відкриває нове), при демонтуванні — закривається. Без cleanup кожне перемонтування компонента створювало б нове WebSocket-з'єднання, що призвело б до витоку пам'яті та множинних коннектів.
useEffect(() => {
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = event => setData(event.data);
// Функція очищення — виконується при демонтуванні та перед перезапуском
return () => {
socket.close();
};
}, []);
За даними React Documentation (2025), AbortController — сучасний підхід для скасування fetch-запитів у cleanup. Якщо ефект робить HTTP-запит, а компонент демонтується до його завершення, запит продовжує виконуватися, і setState після демонтування викликає помилку. Створюй AbortController всередині ефекту та викликай controller.abort() у cleanup, щоб скасувати запит.
Найпоширеніша помилка — пропуск залежностей. Наприклад, ефект використовує пропс userId, але масив залежностей порожній. У результаті ефект виконується один раз із початковим значенням userId і ніколи не реагує на його зміни. Розробник бачить, що компонент отримує новий userId, але дані не оновлюються. eslint-plugin-react-hooks з правилом exhaustive-deps виявляє такі баги автоматично.
// ❌ Race condition — без скасування
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);
// ✅ Виправлено за допомогою AbortController
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${userId}`, { signal: controller.signal })
.then(res => setUser(res));
return () => controller.abort();
}, [userId]);
Для вирішення проблеми нескінченного циклу уникай поміщати в useEffect логіку, яка оновлює стан на основі попереднього стану. Використовуй функціональну форму setState або винось обчислення за межі ефекту. Якщо ефект підписується на сховище або подію браузера, переконайся, що інстанс слухача створюється один раз, а не на кожен рендер.
Часто задавані питання
Напряму — ні, тому що useEffect очікує повернення синхронної функції або undefined. Якщо колбек оголошений як async, він повертає Promise, який React ігнорує, і механізм очищення перестає працювати. Рішення: викликай async функцію всередині ефекту: useEffect(() => { async function load() { ... }; load(); }, []).
Обмеження немає. React рекомендує розділяти непов'язану логіку на окремі useEffect, навіть якщо у них однаковий масив залежностей. Кожен ефект повинен відповідати за одну чітко визначену побічну задачу: один для підписки, інший для завантаження даних, третій для синхронізації заголовка вкладки. Це спрощує розуміння та налагодження.
У React Strict Mode (тільки режим розробки) всі ефекти монтуються, демонтуються і монтуються знову. Це фіча, а не баг — React перевіряє, чи коректно працює cleanup. Якщо після демонтування і повторного монтування ефект веде себе неправильно (наприклад, дублюються підписки), твій cleanup неповний. У продакшені ефект виконується один раз.
Використовуй AbortController. Створи controller всередині ефекту, передай controller.signal у fetch, і в cleanup виклич controller.abort(). Якщо компонент демонтується до завершення запиту, fetch скасується і setState не викличеться. Це запобігає race condition і помилці “Can't perform a React state update on an unmounted component”.
useEffect буде виконуватися після кожного рендеру без винятку. Це означає, що будь-який setState всередині ефекту викличе новий рендер → новий ефект → нескінченний цикл. На практиці ефект без масиву залежностей майже завжди є помилкою. Винятки — логування або синхронізація із зовнішньою системою, де кожен рендер вимагає синхронізації.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також