Dlaczego React useEffect wywołuje się dwa razy w trybie StrictMode 2026

Marek Radoszewski Marek Radoszewski
Języki i Technologie
28.03.2026 9 min
Dlaczego React useEffect wywołuje się dwa razy w trybie StrictMode 2026

Dlaczego React useEffect wywołuje się dwa razy w trybie StrictMode 2026?

Zastanawiasz się, dlaczego React useEffect wywołuje się dwa razy w trybie StrictMode 2026 i skąd bierze się to pozornie dziwne zachowanie? Jeśli na co dzień tworzysz aplikacje w React, zapewne widziałeś, jak Twoje efekty potrafią odpalić się podwójnie w środowisku deweloperskim. Dla wielu osób wygląda to jak błąd lub problem z konfiguracją.

W rzeczywistości nie jest to bug, lecz celowy mechanizm. React wykorzystuje StrictMode, aby zmusić nasz kod do lepszego zarządzania efektami ubocznymi i wychwycić błędy jeszcze przed wdrożeniem do produkcji. To coś w rodzaju wbudowanego recenzenta jakości kodu, który sprawdza, czy nasze komponenty potrafią bezpiecznie przechodzić przez cykle montowania i odmontowywania.

W tym artykule poznasz sedno działania StrictMode, zrozumiesz, czemu useEffect uruchamia się dwa razy, oraz nauczysz się, jak pisać efekty odporne na takie zachowanie. Dzięki temu zjawisko, które wcześniej denerwowało, stanie się dla Ciebie logicznym i przydatnym narzędziem.

StrictMode działa wyłącznie w trybie deweloperskim, więc użytkownicy produkcyjnej wersji Twojej aplikacji nie widzą żadnych skutków tych dodatkowych wywołań. Dla Ciebie natomiast to szansa, by zawczasu przygotować się na przyszłość Reacta, w której częste montowanie i odmontowywanie komponentów będzie standardem w bardziej zaawansowanych scenariuszach.

Programista analizujący w konsoli przeglądarki podwójne wywołanie React useEffect w StrictMode 2026, rozumiejąc cykl mount‑unmount‑mount komponentu

Czym jest StrictMode i dlaczego wpływa na useEffect?

StrictMode jako narzędzie do wykrywania błędów

StrictMode (Tryb Ścisły) to specjalny mechanizm Reacta, który działa tylko podczas developmentu. Jego zadaniem jest wykrywanie potencjalnie niebezpiecznych wzorców, niepoprawnych efektów ubocznych i nieoptymalnych zachowań komponentów. Co ważne, nie zmienia on zachowania aplikacji w produkcji, więc użytkownicy końcowi nie odczuwają jego działania.

Możesz traktować StrictMode jak doświadczonego recenzenta kodu, który celowo stresuje Twoje komponenty. Sprawdza, czy radzą sobie poprawnie, gdy są wielokrotnie montowane i odmontowywane, a także czy ich efekty są prawidłowo czyszczone. To szczególnie ważne tam, gdzie pojawiają się subskrypcje, timery, połączenia sieciowe czy operacje na DOM.

Najważniejsze cele StrictMode to:

  • ujawnianie nieprawidłowych efektów ubocznych, które nie mają funkcji czyszczącej,
  • symulowanie przyszłych scenariuszy concurrent rendering, gdzie komponenty mogą żyć w bardziej dynamicznym środowisku,
  • zmuszanie programisty do pisania kodu bardziej odpornego na zmiany cyklu życia komponentu.

Dlaczego useEffect jest uruchamiany dwa razy?

W kontekście pytania „dlaczego React useEffect wywołuje się dwa razy w trybie StrictMode 2026”, kluczowa jest jedna rzecz: React w Trybie Ścisłym celowo symuluje cykl Mount → Unmount → Mount. Oznacza to, że dla komponentu z efektem:

  1. Komponent zostaje zamontowany, a useEffect się uruchamia.
  2. Komponent jest natychmiast odmontowany, co wywołuje funkcję czyszczącą z useEffect.
  3. Ten sam komponent jest ponownie zamontowany, więc useEffect odpala jeszcze raz.

Dzięki temu widzisz w logach podwójne wywołanie efektu. Nie jest to pomyłka, ale test:

  • czy umieszczasz w efektach poprawną cleanup function,
  • czy Twoje efekty nie powodują wycieków pamięci,
  • czy kod będzie gotowy na bardziej agresywne scenariusze renderowania w przyszłych wersjach Reacta.

Symulacja Mount–Unmount–Mount – jak działa to pod maską?

Szczegółowy przebieg cyklu w StrictMode

Kiedy React w trybie StrictMode napotyka komponent wykorzystujący useEffect, wykonuje w trybie deweloperskim następującą sekwencję kroków:

  1. Pierwszy mount
    Komponent jest montowany, wywoływana jest funkcja komponentu, renderowany jest UI, po czym React uruchamia useEffect.

  2. Szybki unmount komponentu
    React natychmiast odmontowuje komponent. Jeśli useEffect zwrócił funkcję czyszczącą, jest ona uruchamiana. To symuluje normalną sytuację, w której komponent znika z drzewa komponentów.

  3. Drugi mount tego samego komponentu
    Po chwili React ponownie montuje ten sam komponent tak, jakby pojawił się od nowa. Po renderze ponownie wywołuje useEffect, co widzisz jako drugie wywołanie w konsoli.

Ta sekwencja nie występuje w produkcji w takiej formie, jeśli komponent nie jest realnie odmontowywany. Natomiast w dewelopmencie ma ona ogromną wartość, bo pozwala wychwycić efekty, które:

  • coś dodają (np. nasłuchiwacz zdarzeń),
  • ale nie usuwają tego przy odmontowaniu.

To typowy scenariusz prowadzący do błędów i niestabilności aplikacji, który StrictMode ujawnia właśnie przez podwójne wywołanie useEffect.

Efekty uboczne i idempotencja w praktyce z useEffect

Czym są efekty uboczne w React?

W kontekście Reacta efekt uboczny (side effect) to każda operacja, która wpływa na coś spoza czystej logiki renderowania komponentu. Typowe przykłady to:

  • pobieranie danych z API lub innego źródła,
  • ręczna manipulacja DOM-em (np. dodawanie klas, modyfikacja atrybutów),
  • ustawianie timerów (setTimeout, setInterval),
  • dodawanie globalnych słuchaczy zdarzeń (addEventListener),
  • wszelkie subskrypcje (np. WebSocket, kanały eventów, biblioteki zewnętrzne).

React wymaga, by takie operacje były wykonywane w kontrolowany sposób i żeby można je było odwrócić przy odmontowaniu komponentu. Stąd potrzeba funkcji czyszczącej (cleanup function) zwracanej z useEffect.

Idempotencja – dlaczego drugie wywołanie nie powinno psuć logiki?

Kluczowym pojęciem przy analizie, czemu React useEffect wywołuje się dwa razy w StrictMode 2026, jest idempotencja. Operacja idempotentna to taka, którą możesz wykonać wiele razy z tym samym efektem końcowym, co przy jednokrotnym wykonaniu.

Niektóre efekty są z natury idempotentne, np.:

  • pobranie tych samych danych z API dwukrotnie (stan końcowy danych jest taki sam),
  • zapis tych samych wartości w lokalnym stanie, jeśli logika przewiduje takie zachowanie.

Problem pojawia się, gdy:

  • efekt nie jest idempotentny,
  • a jednocześnie nie ma poprawnego czyszczenia.

Przykład problematycznej implementacji:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Efekt uruchomiony!', count); // W StrictMode zobaczysz to dwa razy

    // Wyobraź sobie, że tutaj dodajemy słuchacza zdarzeń bez usuwania go
    // document.addEventListener('click', handleClick);

    // Brak funkcji czyszczącej!
  }, [count]);

  return (
    <div>
      <h1>Licznik: {count}</h1>
      <button onClick={() => setCount(prev => prev + 1)}>Zwiększ</button>
    </div>
  );
}

export default MyComponent;

Gdyby w tym miejscu faktycznie dodać słuchacza kliknięcia bez jego usuwania, to:

  • pierwszy mount dodałby 1 słuchacza,
  • drugi mount (w StrictMode) dodałby kolejnego,
  • każde kliknięcie wywoływałoby handler dwa razy.

To jest dokładnie ten rodzaj błędu, który StrictMode ma ujawnić poprzez podwójne wykonanie useEffect w trakcie developmentu.

Jak poprawnie „sprzątać” w useEffect? (cleanup function)

Wzorzec dobrego efektu z czyszczeniem

Aby kod działał poprawnie w StrictMode i w przyszłych wersjach Reacta, każdy efekt, który coś „dodaje” do świata zewnętrznego, powinien mieć odpowiednie „sprzątanie”. Robi się to poprzez zwrócenie funkcji z useEffect.

Przykład poprawnej implementacji:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);

  useEffect(() => {
    console.log('Efekt ładowania danych uruchomiony!');

    // Symulacja pobierania danych
    const fetchData = async () => {
      try {
        const response = await new Promise(resolve =>
          setTimeout(() => resolve(`Dane dla licznika: ${count}`), 500)
        );
        setData(response);
        console.log('Dane pobrano:', response);
      } catch (error) {
        console.error('Błąd pobierania danych:', error);
      }
    };

    fetchData();

    // Funkcja czyszcząca!
    return () => {
      // Tutaj posprzątamy po efekcie
      console.log('Czyszczenie efektu ładowania danych...');
      setData(null); // Resetujemy dane przy odmontowaniu
    };
  }, [count]); // Efekt uruchamia się przy zmianie count

  useEffect(() => {
    const handleScroll = () => {
      console.log('Użytkownik przewija stronę!');
    };

    console.log('Dodaję słuchacza przewijania...');
    document.addEventListener('scroll', handleScroll);

    return () => {
      console.log('Usuwam słuchacza przewijania...');
      document.removeEventListener('scroll', handleScroll);
    };
  }, []); // Ten efekt uruchamia się raz po zamontowaniu i czyści przy odmontowaniu

  return (
    <div>
      <h1>Licznik: {count}</h1>
      <p>Dane: {data || 'Ładowanie...'}</p>
      <button onClick={() => setCount(prev => prev + 1)}>Zwiększ</button>
    </div>
  );
}

export default MyComponent;

W tym przykładzie dzieje się kilka ważnych rzeczy:

  • Efekt pobierania danych:
  • w StrictMode uruchomi się dwukrotnie,
  • jednak dzięki setData(null) w funkcji czyszczącej, stan zostanie uporządkowany przed kolejnym wywołaniem,
  • zachowanie jest przewidywalne, nawet gdy komponent szybko znika i pojawia się ponownie.

  • Efekt ze słuchaczem przewijania:

  • dodaje scroll listenera,
  • w cleanupie go usuwa,
  • StrictMode najpierw doda, potem usunie, a następnie znowu doda tego słuchacza,
  • ostatecznie w przeglądarce zawsze pozostaje tylko jeden aktywny listener.

To zachowanie można porównać do próby generalnej przed ważnym występem. Najpierw budujesz scenę, testujesz wszystko, potem ją rozkładasz, a na końcu montujesz ponownie. Dzięki temu masz pewność, że nic nie zostało ustawione podwójnie i wszystko zadziała stabilnie, gdy publiczność „wejdzie na produkcję”.

Schemat cyklu życia komponentu React: mount, unmount i ponowny mount z podwójnym wywołaniem useEffect w StrictMode, obrazujący czyszczenie efektów

Kiedy nie musisz się przejmować podwójnym wywołaniem?

Różnica między developmentem a produkcją

Ważny aspekt, który warto jasno podkreślić: podwójne wywołanie useEffect dotyczy wyłącznie trybu deweloperskiego i StrictMode. Po zbudowaniu aplikacji na produkcję:

  • React nie stosuje tej dodatkowej symulacji Mount–Unmount–Mount,
  • efekty uruchamiają się raz dla danego montowania komponentu,
  • ich zachowanie odpowiada „normalnym” oczekiwaniom.

Dlatego nie trzeba panikować, gdy w konsoli deweloperskiej widzisz logi z useEffect pojawiające się dwa razy. To nie oznacza, że użytkownicy Twojej aplikacji będą widzieli podwójne zapytania czy zdublowane subskrypcje w realnym środowisku, o ile wszystko zostało poprawnie wysprzątane.

Efekty czysto wewnętrzne a efekty „brudzące” świat zewnętrzny

W praktyce możesz rozróżnić dwie grupy efektów:

  • Efekty czysto wewnętrzne, np.:
  • prosty console.log,
  • aktualizacja lokalnego stanu, który nie wychodzi poza komponent,
  • logika bez subskrypcji ani operacji na globalnych zasobach.

  • Efekty wpływające na świat zewnętrzny, np.:

  • dodawanie event listenerów do window lub document,
  • subskrypcje WebSocket,
  • ręczne modyfikacje DOM poza kontrolą Reacta,
  • uruchamianie timerów bez ich kasowania.

W pierwszym przypadku podwójne wywołanie w StrictMode zwykle nie robi krzywdy, choć może irytować np. dodatkowymi logami lub dublowaniem zapytań. W drugim przypadku brak funkcji czyszczącej to realne zagrożenie i właśnie te scenariusze React stara się dla Ciebie ujawnić.

Dlatego:

  • jeśli efekt tylko zmienia lokalny stan i nie tworzy trwałych zasobów, możesz podejść do podwójnego wywołania luźniej,
  • ale gdy efekt tworzy subskrypcje, timery, nasłuchiwacze lub połączenia, cleanup jest koniecznością, a StrictMode jest Twoim sprzymierzeńcem.

Kontekst 2026 – przyszłość Reacta a dobre praktyki useEffect

Przygotowanie na concurrent rendering i dynamiczne scenariusze

Wspomniany w pytaniu rok „2026” ma charakter symboliczny i wskazuje na ciągły rozwój Reacta. Kierunek jest jasny: React dąży do coraz bardziej zaawansowanego, współbieżnego renderowania (concurrent rendering) oraz dynamicznego zarządzania komponentami.

W takim świecie:

  • komponenty mogą być częściej montowane i odmontowywane,
  • część pracy może być wykonywana „w tle” i odrzucana,
  • niektóre komponenty mogą być „wstrzymywane”, przechowywane w pamięci i ponownie wykorzystywane.

Jeśli dziś nauczysz się pisać efekty:

  • idempotentne,
  • z poprawnym czyszczeniem,
  • odporne na podwójne wywołanie w StrictMode,

to Twoje aplikacje będą znacznie lepiej przygotowane na nadchodzące funkcje i tryby pracy Reacta. To nie jest jedynie doraźna sztuczka, ale fundamentalna praktyka programistyczna.

Dlaczego warto traktować StrictMode jako inwestycję w jakość kodu?

Patrząc z perspektywy kilku lat do przodu, dobre techniki pracy z useEffect i StrictMode:

  • zmniejszają ryzyko wycieków pamięci oraz trudnych do odtworzenia bugów,
  • sprawiają, że kod jest łatwiejszy do utrzymania w dużych zespołach,
  • są cenione w profesjonalnym środowisku, np. w firmach, gdzie niezawodność i bezpieczeństwo kodu jest kluczowe.

Zamiast więc traktować fakt, że React useEffect wywołuje się dwa razy w trybie StrictMode 2026, jako przeszkodę, warto spojrzeć na niego jak na darmowe narzędzie audytu. Im szybciej przyzwyczaisz się do myślenia w kategoriach: „każdy efekt musi być bezpieczny na powtórne wywołanie i mieć cleanup”, tym stabilniejsze będą Twoje aplikacje.

Podsumowanie

Podwójne wywołanie useEffect w StrictMode to świadome działanie Reacta, a nie błąd frameworka. Dzięki symulacji cyklu Mount–Unmount–Mount:

  • React testuje, czy Twoje efekty mają poprawną funkcję czyszczącą,
  • pomaga wykryć wycieki pamięci i duplikowanie subskrypcji,
  • przygotowuje Twój kod na przyszłe, bardziej złożone scenariusze renderowania.

W produkcji useEffect nie jest uruchamiany dwukrotnie z tego powodu, ale na etapie developmentu StrictMode pełni rolę surowego recenzenta. Jeśli będziesz:

  • zawsze dodawać cleanup function do efektów „brudzących” świat zewnętrzny,
  • dążyć do idempotentności efektów,
  • pamiętać o różnicy między trybem deweloperskim a produkcyjnym,

to podwójne wywołanie przestanie być dla Ciebie zagadką, a stanie się naturalnym elementem pracy z Reactem. Dzięki temu Twoje aplikacje będą bardziej stabilne, skalowalne i gotowe na to, co przyniosą kolejne lata rozwoju ekosystemu.

Marek Radoszewski

Autor

Marek Radoszewski

Freelance developer i tech blogger od 7 lat. Pracował przy projektach dla klientów z Polski, UK i USA. Na blogu pisze o praktycznych aspektach programowania, narzędziach i tym, jak skutecznie rozwijać karierę jako niezależny programista.

Wróć do kategorii Języki i Technologie