useEffect: co to jest, hook efektów ubocznych i cykl życia w React

Autor: IT Sectr Opublikowano: 2026-07-04 Czas czytania: 9 min

useEffect — to hook React, który pozwala wykonywać efekty uboczne w komponentach funkcyjnych, zastępując metody cyklu życia komponentów klasowych: componentDidMount, componentDidUpdate i componentWillUnmount. Według React Documentation (2025), useEffect wykonuje się po tym, jak React zatwierdził zmiany w DOM, co gwarantuje dostęp do aktualnego drzewa DOM. Hook przyjmuje funkcję efektu i opcjonalną tablicę zależności, która kontroluje częstotliwość wykonywania.

Najważniejsze

  • useEffect — hook do wykonywania efektów ubocznych po renderowaniu komponentu.
  • Tablica zależności — kontroluje, kiedy efekt jest restartowany; pusta tablica = jeden raz.
  • Czyszczenie — funkcja cleanup z efektu jest wywoływana przy demontowaniu i przed restartem.
  • Cykl życia — zastępuje componentDidMount, componentDidUpdate i componentWillUnmount.
  • Kolejność wykonywania — efekty uruchamiane są po zatwierdzeniu zmian w DOM.

Czym jest useEffect w React

useEffect — to hook dodany w React 16.8 do wykonywania efektów ubocznych w komponentach funkcyjnych. Przez efekty uboczne rozumiemy operacje, które nie są bezpośrednio związane z renderowaniem UI: zapytania HTTP do API, subskrypcje zdarzeń, praca z timerami, manipulacje DOM, logowanie i integracja z bibliotekami firm trzecich.

Przed pojawieniem się hooków wszystkie te operacje trzeba było umieszczać w metodach cyklu życia komponentów klasowych: componentDidMount do inicjalizacji, componentDidUpdate do reagowania na zmiany propsów, componentWillUnmount do czyszczenia. useEffect połączył wszystkie trzy scenariusze w jednym API, gdzie tablica zależności określa, kiedy efekt powinien się uruchomić. To uprościło logikę i zmniejszyło powielanie kodu, szczególnie w scenariuszach z subskrypcjami.

Według React DevTools Usage Survey (2024), useEffect to drugi najpopularniejszy hook po useState, używany w 89% aplikacji React. Większość programistów używa go do ładowania danych, synchronizacji z systemami zewnętrznymi i zarządzania subskrypcjami zdarzeń DOM.

jsx
import { useEffect } from 'react';

function UserProfile({ userId }) {
    useEffect(() => {
        fetch(`/api/users/${userId}`)
            .then(res => res.json())
            .then(data => setUser(data));
    }, [userId]);
}

Jak działa useEffect: cykl życia efektu

useEffect uruchamia przekazaną funkcję-efekt po tym, jak React zakończył renderowanie i zaktualizował DOM. To kluczowa różnica od obliczeń podczas renderowania: efekt nie blokuje rysowania, co jest krytyczne dla wydajności UX. Gdyby efekty były wykonywane synchronicznie, użytkownik widziałby «zawieszony» interfejs podczas ładowania danych.

Cykl życia typowego efektu składa się z trzech faz. Podczas montowania komponentu React wykonuje efekt. Przy każdej aktualizacji, jeśli zmieniła się przynajmniej jedna zależność z tablicy, React najpierw uruchamia funkcję czyszczenia poprzedniego efektu, a następnie — nowy efekt. Podczas demontowania komponentu wykonywana jest tylko funkcja czyszczenia.

Według React Team — useEffect RFC (2024), wewnętrzna implementacja useEffect używa kolejki efektów ubocznych w drzewie fiber. Po zatwierdzeniu zmian (commit phase) React przechodzi przez tę kolejkę i wywołuje funkcje efektów w kolejności ich deklaracji w komponencie. Każdy węzeł fiber przechowuje odniesienie do poprzedniego efektu dla prawidłowego czyszczenia i restartu.

EtapDziałanie ReactKiedy jest wykonywany
MontowanieWywołanie funkcji efektuPo pierwszym renderowaniu
Aktualizacjacleanup → efektPrzy zmianie zależności
DemontowanieTylko cleanupPrzy usunięciu komponentu

Tablica zależności useEffect

Tablica zależności — drugi argument useEffect — określa, kiedy efekt powinien być restartowany. React porównuje każdą wartość w tablicy z poprzednim renderowaniem za pomocą Object.is. Jeśli przynajmniej jedna wartość się zmieniła, efekt uruchamia się ponownie. Jeśli tablica jest pusta ([]), efekt wykonuje się tylko raz po montowaniu.

Prawidłowy wybór zależności — najtrudniejsza część pracy z useEffect. W tablicy muszą znaleźć się wszystkie zmienne i funkcje, które są używane wewnątrz efektu i mogą się zmieniać między renderowaniami. Pominięcie zależności prowadzi do stale closures — efekt «widzi» nieaktualną wartość z poprzedniego renderowania. Dołączenie zbędnych zależności prowadzi do nadmiernych restartów i potencjalnych błędów.

jsx
// Zależności kontrolują, kiedy efekt jest uruchamiany ponownie
useEffect(() => {
    document.title = `User: ${user.name}`;
}, [user.name]); // uruchom ponownie tylko gdy user.name się zmieni

// eslint-disable-next-line react-hooks/exhaustive-deps
// Jeśli pominiesz zależność, otrzymasz nieaktualne dane

React udostępnia wtyczkę eslint-plugin-react-hooks z regułą exhaustive-deps, która automatycznie sprawdza kompletność tablicy zależności. Według Meta Engineering Blog (2024), włączenie tej wtyczki zmniejsza liczbę błędów związanych z hookami o 72%. Zaleca się poprawianie wszystkich ostrzeżeń exhaustive-deps, a nie wyciszanie ich komentarzem, z wyjątkiem rzadkich przypadków z niestandardową logiką.

useEffect bez zależności i z pustą tablicą

Jeśli nie przekazać tablicy zależności w ogóle, useEffect będzie wykonywany po każdym renderowaniu. Może to być przydatne do synchronizacji z DOM lub logowania, ale najczęściej jest to błąd: efekt wykonuje się zbyt często, co prowadzi do utraty wydajności. W większości przypadków należy przekazać pustą tablicę (jeden raz przy montowaniu) lub tablicę z konkretnymi propsami/state.

Pusta tablica ([]) oznacza, że efekt nie zależy od żadnych wartości i wykonuje się ściśle jeden raz. Jest to analogia componentDidMount w komponentach klasowych. Należy jednak pamiętać: jeśli wewnątrz efektu używane są props lub stan, ale nie są one wymienione w tablicy zależności, efekt będzie używać ich początkowych wartości i nigdy nie zobaczy aktualizacji. Nazywa się to stale capture i często jest źródłem trudnych do wyśledzenia błędów.

Tablica zależnościZachowanieAnalogia z klas
Bez argumentuPo każdym renderowaniucomponentDidUpdate
[]Jeden raz przy montowaniucomponentDidMount
[a, b]Przy zmianie a lub bAnalogia componentWillReceiveProps
cleanup returnZarządzanie demontowaniemcomponentWillUnmount

Czyszczenie efektów w useEffect

Funkcja czyszczenia (cleanup) — to funkcja, którą useEffect może zwrócić ze swojego callbacka. React wywołuje ją przy demontowaniu komponentu i przed ponownym uruchomieniem efektu przy zmianie zależności. Cleanup jest niezbędny do anulowania subskrypcji, timerów, zapytań i wszelkich zasobów, które muszą być zwolnione.

Typowy przykład — subskrypcja WebSocket. Przy montowaniu tworzone jest połączenie, przy aktualizacji zależności — odtwarzane (cleanup zamyka stare, efekt otwiera nowe), przy demontowaniu — zamykane. Bez cleanup każde ponowne montowanie komponentu tworzyłoby nowe połączenie WebSocket, co prowadziłoby do wycieku pamięci i wielu połączeń.

jsx
useEffect(() => {
    const socket = new WebSocket('wss://api.example.com');
    socket.onmessage = event => setData(event.data);

    // Funkcja czyszczenia — uruchamiana przy demontowaniu i przed ponownym uruchomieniem
    return () => {
        socket.close();
    };
}, []);

Według React Documentation (2025), AbortController — nowoczesne podejście do anulowania zapytań fetch w cleanup. Jeśli efekt wykonuje zapytanie HTTP, a komponent zostanie zdemontowany przed jego zakończeniem, zapytanie nadal się wykonuje, a setState po demontowaniu powoduje błąd. Utwórz AbortController wewnątrz efektu i wywołaj controller.abort() w cleanup, aby anulować zapytanie.

Typowe błędy z useEffect

Najczęstszy błąd — pominięcie zależności. Na przykład efekt używa propsa userId, ale tablica zależności jest pusta. W rezultacie efekt wykonuje się raz z początkową wartością userId i nigdy nie reaguje na jego zmiany. Programista widzi, że komponent otrzymuje nowy userId, ale dane nie są aktualizowane. eslint-plugin-react-hooks z regułą exhaustive-deps wykrywa takie błędy automatycznie.

  • Nieskończona pętla — aktualizacja stanu wewnątrz efektu powodująca ponowne renderowanie, które uruchamia efekt ponownie. Rozwiązanie: sprawdź tablicę zależności lub użyj funkcyjnej formy setter.
  • Wyścig zapytań (race condition) — jeśli userId zmienia się szybko, zapytanie dla pierwszego userId może zakończyć się po zapytaniu dla drugiego, a dane pokażą nieprawidłowy wynik. Rozwiązanie: użyj flagi cancelled lub AbortController.
  • Zbędne efekty — łączenie niepowiązanych logik w jednym useEffect. React zaleca dzielenie logiki na kilka efektów, nawet jeśli mają taką samą tablicę zależności.
  • Zapomniane czyszczenie — brak wypisania się ze zdarzeń, czyszczenia timerów lub anulowania zapytań prowadzi do wycieków pamięci i błędów setState po demontowaniu.
jsx
// ❌ Race condition — brak anulowania
useEffect(() => {
    fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);

// ✅ Naprawione za pomocą AbortController
useEffect(() => {
    const controller = new AbortController();
    fetch(`/api/user/${userId}`, { signal: controller.signal })
        .then(res => setUser(res));
    return () => controller.abort();
}, [userId]);

Aby rozwiązać problem nieskończonej pętli, unikaj umieszczania w useEffect logiki, która aktualizuje stan na podstawie poprzedniego stanu. Użyj funkcyjnej formy setState lub wynieś obliczenia poza efekt. Jeśli efekt subskrybuje się do storage lub zdarzenia przeglądarki, upewnij się, że instancja nasłuchiwacza jest tworzona raz, a nie przy każdym renderowaniu.

Często zadawane pytania

Czy można użyć async/await wewnątrz useEffect?

Bezpośrednio — nie, ponieważ useEffect oczekuje zwrotu synchronicznej funkcji lub undefined. Jeśli callback jest zadeklarowany jako async, zwraca Promise, który React ignoruje, a mechanizm czyszczenia przestaje działać. Rozwiązanie: wywołaj funkcję async wewnątrz efektu: useEffect(() => { async function load() { ... }; load(); }, []).

Ile useEffect może być w jednym komponencie?

Nie ma ograniczenia. React zaleca dzielenie niepowiązanej logiki na osobne useEffect, nawet jeśli mają taką samą tablicę zależności. Każdy efekt powinien odpowiadać za jedno jasno określone zadanie poboczne: jeden do subskrypcji, drugi do ładowania danych, trzeci do synchronizacji tytułu zakładki. Upraszcza to zrozumienie i debugowanie.

Dlaczego useEffect wykonuje się dwa razy w StrictMode?

W React Strict Mode (tylko tryb programistyczny) wszystkie efekty są montowane, demontowane i montowane ponownie. To funkcja, a nie błąd — React sprawdza, czy cleanup działa poprawnie. Jeśli po demontowaniu i ponownym montowaniu efekt zachowuje się nieprawidłowo (na przykład duplikują się subskrypcje), twój cleanup jest niekompletny. W produkcji efekt wykonuje się raz.

Jak anulować zapytanie fetch w useEffect?

Użyj AbortController. Utwórz controller wewnątrz efektu, przekaż controller.signal do fetch i w cleanup wywołaj controller.abort(). Jeśli komponent zostanie zdemontowany przed zakończeniem zapytania, fetch zostanie anulowany i setState nie zostanie wywołany. Zapobiega to race condition i błędowi «Can't perform a React state update on an unmounted component».

Co się stanie, jeśli nie przekazać tablicy zależności?

useEffect będzie wykonywany po każdym renderowaniu bez wyjątku. Oznacza to, że każdy setState wewnątrz efektu spowoduje nowe renderowanie → nowy efekt → nieskończoną pętlę. W praktyce efekt bez tablicy zależności prawie zawsze jest błędem. Wyjątki — logowanie lub synchronizacja z systemem zewnętrznym, gdzie każde renderowanie wymaga synchronizacji.

Podsumowanie

  • useEffect — hook do wykonywania efektów ubocznych po zatwierdzeniu DOM, zastępujący componentDidMount, componentDidUpdate i componentWillUnmount.
  • Tablica zależności — kontroluje restart efektu; pusta tablica = jeden raz, pominięcie zależności = stale closure.
  • Cleanup — funkcja czyszczenia jest obowiązkowa dla subskrypcji, timerów i zapytań; bez niej występują wycieki pamięci.
  • AbortController — prawidłowy sposób anulowania zapytań fetch wewnątrz useEffect, zapobiegający race condition.
  • StrictMode — montuje efekt dwukrotnie w trybie deweloperskim w celu sprawdzenia poprawności cleanup.
  • Dziel efekty — każdy useEffect odpowiada za jedno zadanie, nawet jeśli zależności się pokrywają.
  • eslint-plugin-react-hooks — automatycznie sprawdza kompletność tablicy zależności, zmniejszając liczbę błędów o 72%.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również