Пошук уроків, статей та іншого контенту
Розбираємось, навіщо React 18 у StrictMode подвійно викликає ефекти — і чому це не баг.
Компонент монтується — і в консолі двічі з'являється лог із useEffect. Перша реакція — шукати баг у своєму коді. Насправді це навмисна поведінка React.StrictMode у режимі розробки, доданa в React 18, і вона нічого не «ламає» — вона допомагає виявити помилки, які інакше проявилися б лише у продакшені за нещасливого збігу обставин.
У розробницькій збірці StrictMode навмисно монтує компонент, розмонтовує його, а потім монтує знову — імітуючи ситуацію, коли компонент втратив і відновив стан (наприклад, користувач швидко перемкнув вкладку назад). Реальний useEffect виконується лише один раз при монтуванні в продакшн-збірці — подвоєння існує лише в розробці та лише під StrictMode.
useEffect(() => {
console.log("Ефект виконано");
return () => {
console.log("Функція очищення викликана");
};
}, []);
// У StrictMode (розробка): "Ефект виконано" → "Функція очищення викликана" → "Ефект виконано"
// У продакшені: лише "Ефект виконано"Багато ефектів на перший погляд працюють правильно, доки не з'явиться функція очищення, яку забули написати, — підписка на подію, залишена без відписки, інтервал без clearInterval, запит без скасування. Такі витоки роками не проявлялись би в тестах на одному монтуванні, але дають про себе знати в реальному застосунку — особливо там, де компоненти монтуються й розмонтовуються часто (модальні вікна, вкладки, умовний рендеринг).
Подвійний виклик у розробці — це стрес-тест саме для функції очищення: якщо ефект написаний правильно (кожна підписка супроводжується відповідною відпискою), подвійне монтування абсолютно непомітне для користувача й не впливає на дані.
// Проблемний варіант — немає функції очищення
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000);
// забута функція очищення — у StrictMode тепер запущено ДВА таймери,
// і лічильник рахує вдвічі швидше, ніж очікувалось
}, []);
// Правильний варіант
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(id); // прибирає таймер попереднього "монтування"
}, []);Вимикати StrictMode, щоб «позбутись» подвійного виклику — це ховає симптом, а не виправляє причину.
Додавати прапорець на кшталт useRef(false), щоб пропустити другий виклик, — це маскує відсутність правильного очищення замість того, щоб його дописати.
Панікувати через подвійні мережеві запити в розробці — якщо API-виклик справді дублюється у продакшені, шукайте причину в логіці самого ефекту, а не в StrictMode.
Подвійний виклик useEffect у розробці — навмисний, тимчасовий діагностичний інструмент React 18's StrictMode, а не поведінка продакшн-збірки. Правильна реакція — переконатись, що кожен ефект із побічним ефектом (підписка, таймер, запит) має відповідну функцію очищення, а не вимикати StrictMode чи обходити подвійний виклик штучно.