StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
ReactHooksArchitecture

useEffect to prawie zawsze błąd — kiedy go naprawdę potrzebujesz, a kiedy nie

useEffect jest najczęściej nadużywanym hookiem w React. Pokazuję 7 wzorców, gdzie useEffect jest antywzorcem, i konkretne alternatywy: Server Actions, useSyncExternalStore, events, derived state.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
13 maja 2026 08:16
Czytanie
5 min czytania
Aktualizacja
Wersja pierwotna

W skrócie

  • useEffect służy do synchronizacji z systemami poza Reactem, nie do zwykłych obliczeń i reakcji na event.
  • Derived state licz w renderze, zamiast trzymać go w osobnym stanie aktualizowanym effectem.
  • Fetching danych w aplikacji lepiej obsłużyć przez Server Components renderują się po stronie serwera i mogą pobierać dane bez wysyłania dodatkowego kodu komponentu do przeglądarki., TanStack Query albo SWR.
  • Jeśli effect nie dotyka świata zewnętrznego, prawdopodobnie da się go usunąć.

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

Code
// ŹLE
function Checkout({ items }) {
  const [total, setTotal] = useState(0);
 
  useEffect(() => {
    setTotal(items.reduce((sum, item) => sum + item.price, 0));
  }, [items]);
 
  return <p>Total: {total} zł</p>;
}

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.

Code
// DOBRZE
function Checkout({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0);
 
  return <p>Total: {total} zł</p>;
}

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

Code
// ŹLE
function Toggle({ onChange }) {
  const [isOn, setIsOn] = useState(false);
 
  useEffect(() => {
    onChange(isOn);
  }, [isOn]);
 
  return <button onClick={() => setIsOn(!isOn)}>{isOn ? 'On' : 'Off'}</button>;
}

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ł).

Code
// DOBRZE — event handler wywołuje callback bezpośrednio
function Toggle({ onChange }) {
  const [isOn, setIsOn] = useState(false);
 
  const handleClick = () => {
    const next = !isOn;
    setIsOn(next);
    onChange(next);
  };
 
  return <button onClick={handleClick}>{isOn ? 'On' : 'Off'}</button>;
}

Jeśli state pochodzi od rodzica, jeszcze prościej:

Code
// JESZCZE LEPIEJ — state w rodzicu
function Toggle({ isOn, onChange }) {
  return <button onClick={() => onChange(!isOn)}>{isOn ? 'On' : 'Off'}</button>;
}

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

Code
// ŹLE
function Profile({ userId }) {
  const [comment, setComment] = useState('');
 
  useEffect(() => {
    setComment('');  // reset po zmianie usera
  }, [userId]);
 
  return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}

Ten pattern znowu renderuje komponent dwukrotnie (najpierw ze starym commentem, potem z resetowanym). A rozwiązanie jest trywialne:

Code
// DOBRZE — używamy key, żeby remount komponentu
function ProfilePage({ userId }) {
  return <Profile userId={userId} key={userId} />;
}
 
function Profile({ userId }) {
  const [comment, setComment] = useState('');  // reset automatyczny przy zmianie key
  return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}

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

Code
// ŹLE (klasyczny wzorzec w dokumentacji Reacta sprzed 2022)
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    let cancelled = false;
    setLoading(true);
 
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });
 
    return () => { cancelled = true; };
  }, [userId]);
 
  if (loading) return <Spinner />;
  if (error) return <Error error={error} />;
  return <Profile user={user} />;
}

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).

Code
// DOBRZE — TanStack Query
import { useQuery } from '@tanstack/react-query';
 
function UserProfile({ userId }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  });
 
  if (isLoading) return <Spinner />;
  if (error) return <Error error={error} />;
  return <Profile user={user} />;
}

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:

Code
// JESZCZE LEPIEJ — async Server Component
async function UserProfile({ userId }: { userId: string }) {
  const user = await getUser(userId);
  return <Profile user={user} />;
}

Antywzorzec 5: Inicjalizacja state'u z propsów

Code
// ŹLE
function Editor({ initialValue }) {
  const [value, setValue] = useState('');
 
  useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);
 
  return <textarea value={value} onChange={e => setValue(e.target.value)} />;
}

Znowu — dwa rendery, potencjalne race conditions, user traci swoje zmiany jeśli prop się zmieni.

Code
// DOBRZE — lazy initial state
function Editor({ initialValue }) {
  const [value, setValue] = useState(initialValue);  // tylko przy mount
 
  return <textarea value={value} onChange={e => setValue(e.target.value)} />;
}

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

Code
// ŹLE
function SearchBox() {
  const [query, setQuery] = useState('');
 
  useEffect(() => {
    if (query.length > 2) {
      trackSearch(query);  // analytics
    }
  }, [query]);
 
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

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.

Code
// DOBRZE
function SearchBox() {
  const [query, setQuery] = useState('');
 
  const handleChange = (e) => {
    const next = e.target.value;
    setQuery(next);
    if (next.length > 2) {
      trackSearch(next);
    }
  };
 
  return <input value={query} onChange={handleChange} />;
}

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

Code
// ŹLE
function OnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
 
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
 
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
 
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
 
  return <span>{isOnline ? 'Online' : 'Offline'}</span>;
}

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:

Code
// DOBRZE — useSyncExternalStore
import { useSyncExternalStore } from 'react';
 
function subscribe(callback) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}
 
function OnlineStatus() {
  const isOnline = useSyncExternalStore(
    subscribe,
    () => navigator.onLine,          // klient
    () => true,                       // SSR (domyślnie online)
  );
 
  return <span>{isOnline ? 'Online' : 'Offline'}</span>;
}

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:

Code
function FocusTrap() {
  const ref = useRef(null);
 
  useEffect(() => {
    const element = ref.current;
    const trap = new FocusTrap(element);
    trap.activate();
    return () => trap.deactivate();
  }, []);
 
  return <div ref={ref}>...</div>;
}

2. Manipulacja document / window:

Code
function TitleUpdater({ title }) {
  useEffect(() => {
    document.title = title;
  }, [title]);
 
  return null;
}

3. Setting up timers / intervals:

Code
function Clock() {
  const [time, setTime] = useState(new Date());
 
  useEffect(() => {
    const id = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(id);
  }, []);
 
  return <p>{time.toLocaleTimeString()}</p>;
}

4. Subscribe do biblioteki nie-Reactowej:

Code
function MapView() {
  const ref = useRef(null);
 
  useEffect(() => {
    const map = L.map(ref.current).setView([50.06, 19.94], 13);  // Leaflet
    L.tileLayer(TILE_URL).addTo(map);
    return () => map.remove();
  }, []);
 
  return <div ref={ref} style={{ height: 400 }} />;
}

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.

Code
import { useEffect, useEffectEvent } from 'react';
 
function ChatRoom({ roomId, muted }: { roomId: string; muted: boolean }) {
  const onConnected = useEffectEvent(() => {
    if (!muted) {
      showToast('Połączono z pokojem');
    }
  });
 
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.on('connected', onConnected);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
}

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.

Uwaga

useEffectEvent nie służy do ukrywania brakujących dependencies. Używaj go tylko dla logiki, która jest częścią effectu, ale sama nie powinna powodować ponownej synchronizacji.

Diagnostyczne pytania

Kiedy zamierzam napisać useEffect, przechodzę przez checklist:

  1. Czy to jest transformacja danych? Jeśli wartość da się obliczyć z props/state w renderze — nie używaj effect.
  2. Czy to reakcja na event użytkownika? Jeśli tak — logika idzie do handlera.
  3. Czy to fetchowanie danych? Użyj TanStack Query / SWR albo Server Components.
  4. Czy to synchronizacja z external system? Tak — useEffect jest odpowiedni. useSyncExternalStore dla stores.
  5. 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.

Często zadawane pytania

Pracuję z tym zawodowo.

Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.

Skontaktuj się ze mną
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Moje artykułyWięcej o mnie

Seria

React w praktyce 2026
  1. 1React 19 Actions — formularz bez onSubmit, useOptimistic i useActionState w praktyce
  2. 2React Compiler w 2026 — czy useMemo i useCallback są już martwe?
  3. 3React Query (TanStack) vs SWR vs useEffect — kompletny przewodnik po fetchingu w 2026
  • Krótko — po co useEffect istnieje1 min
  • Antywzorzec 1: Transformacja state'u w useEffect1 min
  • Antywzorzec 2: Powiadamianie rodzica o zmianie1 min
  • Antywzorzec 3: Resetowanie state'u gdy zmienia się prop1 min
  • Antywzorzec 4: Fetching danych1 min
  • Antywzorzec 5: Inicjalizacja state'u z propsów1 min
  • Antywzorzec 6: Event handlery w useEffect1 min
  • Antywzorzec 7: Subskrypcja do external store1 min
  • Kiedy useEffect JEST poprawny1 min
  • useEffectEvent — gdy effect potrzebuje najnowszych danych1 min
  • Diagnostyczne pytania1 min
  • Podsumowanie1 min

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
SEO w Astro — Core Web Vitals, dane uporządkowane i techniczny fundament rankingu w 2026
SEO w Astro — Core Web Vitals, dane uporządkowane i techniczny fundament rankingu w 2026

Jak zbudować stronę w Astro, która dominuje w SEO — Core Web Vitals, sitemap, robots.txt, metadane, dane uporządkowane i GEO/AEO. Przewodnik techniczny z konkretnymi implementacjami.

Maciej Sala

Maciej Sala

Founder Strivelab

13 maja 2026
React Query (TanStack) vs SWR vs useEffect — kompletny przewodnik po fetchingu w 2026
React Query (TanStack) vs SWR vs useEffect — kompletny przewodnik po fetchingu w 2026

Jak pobierać dane w React w 2026? Porównanie TanStack Query, SWR i useEffect. Kiedy Server Components wystarczą, kiedy potrzebujesz cache, invalidation, optimistic updates i infinite queries.

Maciej Sala

Maciej Sala

Founder Strivelab

13 maja 2026
React 19 Actions — formularz bez onSubmit, useOptimistic i useActionState w praktyce
React 19 Actions — formularz bez onSubmit, useOptimistic i useActionState w praktyce

React 19 Actions zmieniają sposób pisania formularzy. Przewodnik po useActionState, useOptimistic, useFormStatus i akcjach na atrybucie form action. Z przykładami i migracją z onSubmit.

Maciej Sala

Maciej Sala

Founder Strivelab

13 maja 2026