useEffect uruchamia kod po renderze komponentu i służy głównie do synchronizacji Reacta z systemami zewnętrznymi. to najczęściej nadużywany hook w React. W audytach kodu widzę regularnie, że 70–80% użyć useEffect nie powinno tam być — albo rozwiązują problem, który nie powinien istnieć, albo duplikują mechanizm, który React już daje w standardzie.
Sam zespół React wprost pisze w dokumentacji: „You might not need an Effect". Ale mentalny model „masz problem → użyj useEffect" jest tak zakorzeniony, że nawet doświadczeni developerzy sięgają po ten hook odruchowo. W tym artykule pokazuję siedem najczęstszych antywzorców i konkretne alternatywy.
Krótko — po co useEffect istnieje
useEffect ma jedno zadanie: synchronizacja Reacta z systemami spoza Reacta. Subscribe do WebSocketa, dodanie event listenera do window, manipulacja DOM-em, który jest poza kontrolą Reacta, integracja z third-party biblioteką jak Leaflet czy D3.
Wszystko inne — obliczenia na podstawie state'u, reakcja na event, pobranie danych z serwera, powiadomienie rodzica o zmianie — ma swoje lepsze alternatywy, które React 19 w większości przypadków oferuje out of the box.
Antywzorzec 1: Transformacja state'u w useEffect
Ten pattern wydaje się naturalny — „mam listę itemów, muszę policzyć total, więc używam effect". Problem: powoduje dwa rendery dla każdej zmiany. Pierwszy render pokazuje stare total, potem effect odpala się, ustawia nowe total, drugi render pokazuje poprawną wartość. Migotanie, niepotrzebna praca Reacta.
Wartości obliczone bezpośrednio w renderze to tzw. Derived state to wartość wyliczana z propsów lub state, której zwykle nie trzeba trzymać w osobnym stanie. — React automatycznie je przelicza przy każdej zmianie propsów/state'u. Z React Compilerem są automatycznie memoizowane.
Antywzorzec 2: Powiadamianie rodzica o zmianie
Wzorzec „zmieniam state, potem effect odpala callback do rodzica" jest klasycznym źródłem infinite loopów i niechcianych efektów przy pierwszym renderze (effect odpali się natychmiast z isOn = false, chociaż user niczego nie zrobił).
Jeśli state pochodzi od rodzica, jeszcze prościej:
Zasada: event to event, effect to effect. Jeśli user coś kliknął, logika idzie do handlera kliknięcia. Effect jest dla rzeczy, które dzieją się „bez udziału użytkownika" (timer, websocket, resize).
Antywzorzec 3: Resetowanie state'u gdy zmienia się prop
Ten pattern znowu renderuje komponent dwukrotnie (najpierw ze starym commentem, potem z resetowanym). A rozwiązanie jest trywialne:
key to potężne narzędzie — zmiana key mówi Reactowi „to jest nowa instancja komponentu, zresetuj cały state". Działa dla dowolnie skomplikowanego drzewa.
Antywzorzec 4: Fetching danych
Ten kod działa. Ale nie ma cache'owania, nie ma deduplikacji (jeśli trzy komponenty fetchują tego samego usera, dostajemy trzy requesty), nie ma retry, nie ma optimistic updates, nie obsługuje race conditions (user zmienia userId szybko — odpowiedzi mogą przyjść w złej kolejności).
Pięć linii kodu, dostajesz wszystko: cache, deduplikację, retry, background refetch, optimistic updates. Szczegółowo opisuję to w artykule o fetchingu danych w React.
W Server Components to jeszcze prostsze:
Antywzorzec 5: Inicjalizacja state'u z propsów
Znowu — dwa rendery, potencjalne race conditions, user traci swoje zmiany jeśli prop się zmieni.
useState(initialValue) używa initialValue tylko przy pierwszym renderze. Późniejsze zmiany initialValue są ignorowane — co zazwyczaj jest zachowaniem, którego chcesz. Jeśli naprawdę potrzebujesz resetu przy zmianie — użyj key jak w antywzorcu 3.
Antywzorzec 6: Event handlery w useEffect
Problem: trackSearch odpala się również przy pierwszym renderze (jeśli query ma wartość z URL albo localStorage). A chcemy trackować tylko realne wyszukiwania usera.
Trackowanie (analytics, logi, wysyłka danych) zawsze powinno być triggerowane przez konkretne user action, nie przez pośredni state change.
Antywzorzec 7: Subskrypcja do external store
Ten kod działa, ale jest podatny na bug'i w SSR (hydration mismatch — serwer widzi true, klient może widzieć false). React 18+ dostarcza hook specjalnie do tego:
useSyncExternalStore pozwala bezpiecznie subskrybować zewnętrzny store w sposób zgodny z concurrent renderingiem Reacta. jest zaprojektowany do subskrypcji external sources (window events, Redux store, localStorage) i obsługuje SSR, tearing i concurrent mode. Jeden hook zamiast kombinacji useState + useEffect.
Kiedy useEffect JEST poprawny
Żeby nie było, że całkowicie go odrzucam — są miejsca, gdzie useEffect jest dokładnie tym, czego potrzebujesz.
1. Integracja z DOM API niezintegrowanym z React:
2. Manipulacja document / window:
3. Setting up timers / intervals:
4. Subscribe do biblioteki nie-Reactowej:
W każdym z tych przypadków synchronizujesz Reacta z czymś zewnętrznym, co nie wie o istnieniu Reacta. To jest przypadek użycia useEffect.
useEffectEvent — gdy effect potrzebuje najnowszych danych
React 19.2 dodaje useEffectEvent, które rozwiązuje częsty problem: effect powinien pozostać podłączony do zewnętrznego systemu, ale callback wewnątrz niego ma widzieć najnowsze propsy albo state.
Bez useEffectEvent kuszące jest dopisanie muted do dependency array, co powoduje reconnect chatu przy każdym przełączeniu wyciszenia. Z useEffectEvent effect reaguje na roomId, a callback nadal widzi najnowsze muted.
Diagnostyczne pytania
Kiedy zamierzam napisać useEffect, przechodzę przez checklist:
- Czy to jest transformacja danych? Jeśli wartość da się obliczyć z props/state w renderze — nie używaj effect.
- Czy to reakcja na event użytkownika? Jeśli tak — logika idzie do handlera.
- Czy to fetchowanie danych? Użyj TanStack Query / SWR albo Server Components.
- Czy to synchronizacja z external system? Tak —
useEffectjest odpowiedni.useSyncExternalStoredla stores. - Czy to inicjalizacja, która powinna się wydarzyć raz? Użyj
useState(() => init()), ref, albo top-level module code.
Jeśli żadna z pozycji 1–4 nie jest prawdą — prawdopodobnie nie potrzebujesz useEffect.
Podsumowanie
useEffect nie jest zły. Jest po prostu nadużywany do problemów, dla których istnieją lepsze rozwiązania. W 2026 roku, ze wszystkimi narzędziami, które mamy — Server Components, Server Actions, TanStack Query, useSyncExternalStore, useActionState, useTransition — większość kodu, który pisałem trzy lata temu z useEffect, dziś bym napisał zupełnie inaczej. I to nie tylko „czystszy", ale też realnie mniej podatny na bug'i.
Jeśli masz projekt React z masą useEffect i chcesz go zmodernizować — napisz do mnie, audyt z konkretnymi sugestiami to typowa usługa StriveLab.
