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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

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

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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

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

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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

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

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
  • Audyt SEO i Performance
  • Testy automatyczne i QA
  • Konsultacje Produktowe
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Next.jsReact

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

TanStack Query, SWR czy useEffect — które wybrać w 2026? I kiedy Server Components sprawiają, że to pytanie w ogóle nie ma sensu?

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
24 kwietnia 2026 00:00
Czytanie
10 min czytania
Aktualizacja
20 maja 2026 09:08

Artykuł w skrócie

  • Pakowanie się w useEffect i fetch to najkrótsza droga do zbudowania własnego, bardzo słabego systemu cache'owania i odświeżania.
  • TanStack Query to obecnie domyślny wybór w projektach, które muszą solidnie zarządzać stanem serwerowym po stronie przeglądarki.
  • SWR to lekki plan B do mniejszych projektów, gdzie wystarczy prosta polityka stale-while-revalidate.
  • Server Components zdejmują z frontendu dużą część zapytań, ale nie zastępują w pełni interaktywnego cache'owania po stronie klienta.

Data fetching to pobieranie danych z serwera lub API oraz obsługa cache, błędów, retry i stanu ładowania., czyli sztuka sensownego pobierania danych, to bodajże najstarszy problem w świecie Reacta i jednocześnie decyzja, którą architekci systemów potrafią najmocniej zaniedbać. Napisanie podstawowego useEffect wraz z fetch jest dziecinnie proste. Jednak dopisanie do niego logiki, która poprawnie obsłuży cache, usunie zdublowane zapytania, bezbłędnie zadba o powtórzenia w razie błędów (retry) i elegancko wybroni się przed nadpisywaniem starych wyników (race conditions)... to już temat na kilka dobrych miesięcy ciężkiej pracy całego zespołu.

Mamy 2026 rok i na stole wylądowały w zasadzie trzy dominujące rozwiązania: potężny kombajn TanStack Query, zwinny i lekki pakiet SWR od Vercela oraz uparty klasyk useEffect (stosowany głównie przez zespoły cierpiące na alergię na dodawanie nowych bibliotek do pliku package.json). W tym artykule zderzymy ze sobą całą trójkę i sprawdzimy, jak w to wszystko wpisują się rewelacyjne Server Components.

Uwaga

Zróbmy od razu miejsce na pewne sprostowanie: ani TanStack Query, ani SWR NIE ZASTĘPUJĄ rozwiązań typu Redux czy Zustand. To narzędzia z zupełnie innej bajki! Zarządzają one tzw. Server State. Od stanów menu, koszyka czy filtrów mamy Client State (czyli właśnie Zustand). W każdym dojrzałym projekcie z 2026 roku te dwa obszary współpracują ze sobą ramię w ramię.

Hierarchia środowiska – ścieżka podejmowania decyzji na skróty

Nie masz czasu na długie lektury? Oto ściągawka w 30 sekund:

  1. Server Components (w Next.js lub Remix) — Twoja pierwsza linia frontu. Jeśli coś da się zaciągnąć i wrzucić do HTML-a bezpośrednio z poziomu serwera, zrób to tam. Nie martwisz się o logikę cache'owania, unikasz wpychania JavaScriptu do przeglądarki.
  2. TanStack Query — Podstawowe działo, jeśli jednak potrzebujesz uderzyć po dane z poziomu frontendu (klienta). Potężne, stabilne i przeładowane niezbędnymi funkcjami.
  3. SWR — Gdy budujesz coś bardzo minimalistycznego i potrzebujesz jedynie leciutkiej odskoczni pod podstawowe odpytywania.
  4. Opcja z useEffect + fetch — Zakazane słowo przy twardej architekturze korporacyjnej. Trzymaj to z dala od środowisk produkcyjnych.

Dlaczego useEffect potrafi mocno podciąć skrzydła?

Zacznijmy w ogóle od tego, na jakich minach kładzie nas klasyczne, podręcznikowe podejście:

Code
function UserProfile({ userId }: { userId: string }) {
  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((r) => r.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} />
}

Powyższy blok kodu technicznie... działa. Problem polega na tym, że skrywa mnóstwo architektonicznych potknięć:

1. Wycieki na zasobach bazy (Brak cache'owania). Wyobraź sobie, że wchodzisz w zakładkę /users/123, potem skaczesz pod /users/456 by za moment znów wrócić na stare /users/123. Przeglądarka zaciągnie kod od zera za każdym jednym razem. Choć rekordy w bazie się nie zmieniły – Ty niepotrzebnie płacisz za to w rachunkach do serwera.

2. Marnowanie łącza (Brak deduplikacji). Jeżeli na jednej stronie pięć różnych małych komponentów zechce odczytać dane z /api/users/123, "goły" fetch potulnie wyśle w świat pięć osobnych requestów. Kompletny absurd i to bardzo powszechny.

3. "Kto pierwszy ten lepszy" (Race conditions). Nawet z opcją zmyślnej blokady (flaga cancelled), jeśli szybko przebierasz między podstronami, asynchroniczne odpowiedzi mogą spłynąć do komponentu w całkowicie złej kolejności. Twój kod wysypie starą odpowiedź, chociaż przed momentem pytałeś już o nową.

4. Panika przy błędach (Brak retry). Padło Ci lokalne połączenie na ułamek sekundy w pociągu? Koniec. Użytkownik dostaje brzydki Error i jeśli sam nie wciśnie klawisza f5 (odśwież), utknie tam na amen.

5. Złe doświadczenia w interfejsie (Brak odświeżeń w tle). Odchodzisz od biurka, w międzyczasie Twoi znajomi dopisali wpisy w grupie. Wracasz za kwadrans, ale strona twardo wisi na tym co widziała przed wyjściem. Bez odświeżania na tło nie wiesz nawet, że informacje są przestarzałe.

6. Frustrujący wskaźnik ładowania (Brak Optymistycznych Aktualizacji). Zostawiasz komentarz, po czym wlepiasz wzrok w kręcące się kółko, modląc się, by proces przebiegł pomyślnie. Nowoczesne systemy pokazują Twój komentarz od razu, nie każąc Ci czekać na zielone światło od API.

Widzisz problem? Oczywiście, jako dzielny rzemieślnik mógłbyś to obudować samemu w kodzie, ale z ręką na sercu — napisanie tych warstw od nowa to tysiące linii kodu i setki potknięć (bugów). Po co wyważać otwarte drzwi, skoro inni mądrzy specjaliści od bibliotek zrobili to świetnie przed nami?

TanStack Query — Prawdziwy hegemon roku 2026

TanStack Query (w starych dziejach używany pod szyldem React Query) to paczka, która powyższe problemy kompresuje do śmiesznie prostych dziesięciu linijek kodu.

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

Tylko tyle. Co w pakiecie dostarcza Ci ten kod?

  • Genialnie prosty bufor - rozbija cache na podstawie podanego klucza (czyli np. ['user', userId]). Każdy kolejny komponent podpięty pod ten klucz, bez pardonu doczepi się do wyniku i daruje sobie szukanie danych na świeżo w bazie.
  • Magiczna Deduplikacja - jak już wcześniej wspomniałem, wszystkie prośby równoległe zostają zebrane i wysłane jednym zgrabnym "kurierem" (promisem).
  • Walka do końca (Retry) - Domyślnie skrypt walczy jeszcze trzykrotnie, po równych odstępach odrzucenia (exponential backoff) zanim definitywnie "rzuci ręcznikiem".
  • Natychmiastowe aktualizacje dla wracających - Zmienisz kartę by sprawdzić maila? TanStack na moment przed Twoim powrotem odpyta API, żeby zaserwować Ci na ekran najbardziej "świeżą" treść.
  • Rewalidacja Stale-while - Pokaże to, co do tej pory uzbierał w buforze w błyskawicznym trybie, żeby tylko w tle móc doładować i uaktualnić wartości.

Przechodzimy do małego wdrożenia (Setup)

Code
npm install @tanstack/react-query

Teraz mała owijka dla reszty aplikacji:

Code
// src/providers.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
 
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // Ile czasu dane są u nas uznawane za tzw. świeże (tu ucięte po 1 minucie)
      gcTime: 5 * 60 * 1000, // Jak długo aplikacja przetrzymuje klucze i historię w nieużywanej zakładce - śmieciarz odpala po 5 minutach!
    },
  },
})
 
export function Providers({ children }) {
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

Mała podpowiedź jeśli pracujesz nad stosem od Next.js z App Routerem — tam upewnij się, żeby Twój wbudowany "klient do zapytań" stał się funkcją opartą per każde wejście a nie o jeden instancyjny, wspólny "singleton"! Dokumentacja na oficjalnych stronach przeprowadzi Cię przez to wzorowo.

Wbudowane, agresywne domyślne zasady od twórców (Które trzeba znać by nie płakać!)

TanStack nie "prosi". TanStack narzuca swoje żelazne i niezwykle czułe ramy na cały framework. Nieznajomość tych domyślnych funkcji bywa niezwykle myląca dla programistów:

Ustawienie StartoweZa co opowiada u podstaw?Kiedy powinieneś interweniować i je przesterować?
staleTime: 0W tej paczce wszystko, dosłownie "natychmiast" bywa traktowane za przeterminowane (stale). Odświeżenia i pule pobrań w tle odpalają w sekundę po przeładowaniach widoków!Odmierz spokojnie np 60 do 120 sekund czasu dla mało krytycznych miejsc pod odświeżanie serwerowe by portfele u inwestora nie poszły z ogniem.
gcTime: 5 * 60 * 1000Gdyby skrypt o tobie kompletnie zapomniał (bo przeszedłeś w panel na całkowicie inny model komponentowy) to po pełnych pięciu minutach zapas bufora poleci "w kosz".Wyjdź w wyższe ramy, kiedy użytkownicy Twojego klienta notorycznie lubią wracać do wielkich rozstrzelanych i wczytanych wcześniej wielkich raportów.
retry: 3Każde polegnięcie API z reguły stawi czoła walce ponownej (z tzw wariantem podwójnego odstępu w próbach).Kompletnie wyklucz z tego wyścigu m.in ścieżki w procesach opartych o błędy autoryzacji. W końcu błędne hasło to błędne hasło.
refetchOnWindowFocus: truePowrót w zakładce czy nawet kliknięcie po obiekcie do ekranu wyrzuci ponowne przeładowanie zapytań serwera.Możesz to temperować po wspomnianej wcześniej zaporze czasowej u "StaleTime".
Notatka

Częstą, wręcz standardową "łatą" przy przeglądach kodów w TanStack Query nie jest wyłączenie powrotu zapytań, ale mądre ustawienie w systemie zmiennych pod staleTime na każdy kluczowy pakiet informacyjny!

Co ze zmianami pod dane? Wyprowadzamy Mutacje

Code
import { useMutation, useQueryClient } from '@tanstack/react-query'
 
function AddComment({ postId }: { postId: string }) {
  const queryClient = useQueryClient()
 
  const mutation = useMutation({
    mutationFn: (text: string) =>
      fetch(`/api/posts/${postId}/comments`, {
        method: 'POST',
        body: JSON.stringify({ text }),
      }),
    onSuccess: () => {
      // Skasowaliśmy bufor dla tego posta by zmusić paczkę do uaktualnień!
      queryClient.invalidateQueries({ queryKey: ['comments', postId] })
    },
  })
 
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        const text = new FormData(e.currentTarget).get('text') as string
        mutation.mutate(text)
      }}
    >
      <input name="text" />
      <button disabled={mutation.isPending}>Wyślij</button>
    </form>
  )
}

Jeden mały wpis a magii znowu mamy pod dostatkiem. Kiedy zapytanie przejdzie test "Zrobiono Pomyślnie" (onSuccess), bufor dla wpisów o tagu komentarzy (dla danego identyfikatora wpisu postId) natychmiast ulega przedawnieniu. Rezultat? Skrypt "TanStacka" wykrzykuje o nowym powiewie informacji od bazy i ładuje na widok zaktualizowaną dyskusję dla każdego użytkownika operującego obecnie pod tym wpisem!

Oczaruj klienta optymistycznymi wczytywaniami (Optimistic updates)

Kto chce czekać po wysłaniu posta by powitać potwierdzenie bycia odebranym od opieszałego pociągu komunikacyjnego dla bazy po np dalekich serwerach Amerykańskich (z tzw strzałów post na logice asynchronicznej)? Zbuduj na ekranach opcję rzucenia powiadomienia a'la Messenger: "Widać że weszło bezbłędnie z mety, czekamy na puste weryfikacje za kulisami w kodzie".

Code
const mutation = useMutation({
  mutationFn: (text: string) => addComment(postId, text),
  onMutate: async (text) => {
    // Wciskasz hamulec by w tym wariancie nic Ci się po bazie przez ułamki na ekranach przypadkiem nie ułożyło z opóźnień:
    await queryClient.cancelQueries({ queryKey: ['comments', postId] })
 
    // Zrób fotkę ze stanu do tej pory wypracowanych rozmów (na wypadek katastrofy sieci i potrzeby cofnięć awaryjnych)
    const previousComments = queryClient.getQueryData(['comments', postId])
 
    // Optymistycznie dodajemy komentarz do cache, zanim serwer potwierdzi zapis
    queryClient.setQueryData(['comments', postId], (old: Comment[]) => [
      ...old,
      { id: 'temp-' + Date.now(), text, createdAt: new Date() },
    ])
 
    return { previousComments }
  },
  onError: (err, text, context) => {
    // Jeżeli faktycznie jednak poszło coś do sieci źle, wgrywasz bezpiecznie zapasowy punkt dawnej dyskusji bez żadnego potu
    queryClient.setQueryData(['comments', postId], context.previousComments)
  },
  onSettled: () => {
    // Potem, by ukrócić wszystkie wariacje "szkieletu kodu u fałszywek bufora", żądasz rzutowania w stan "odśwież natychmiast całą logikę komentarza by odetchnąć i zamknąć puste identyfikatory logiki "temp-Date.now()""
    queryClient.invalidateQueries({ queryKey: ['comments', postId] })
  },
})

Cały kod zajmuje trochę więcej niż nowe bajery po paczce w postaci haków w najnowszych, bazowych systemach (np useOptimistic od React z lat 19) ale uwierz mi — otrzymujesz na nim wielokrotnie precyzyjniejsze kontrolki kierownicze.

Ciągnące się w nieskończoność skrypty od list (Infinite queries)

Paginacje na "Load More" dla systemów bazodanowych?

Code
import { useInfiniteQuery } from '@tanstack/react-query'
 
function PostList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useInfiniteQuery({
      queryKey: ['posts'],
      queryFn: ({ pageParam = 1 }) =>
        fetch(`/api/posts?page=${pageParam}`).then((r) => r.json()),
      getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
      initialPageParam: 1,
    })
 
  return (
    <>
      {data?.pages.map((page, i) => (
        <Fragment key={i}>
          {page.posts.map((post) => (
            <PostCard key={post.id} post={post} />
          ))}
        </Fragment>
      ))}
 
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Ładowanie w toku...' : 'Dawaj więcej wpisów!'}
        </button>
      )}
    </>
  )
}

Jeśli przy okazji renderujesz bardzo długie listy wyników, połącz to z wirtualizacją, by nie obciążać przeglądarki tysiącami elementów DOM — szczegóły w artykule Wirtualizacja list w React — kiedy TanStack Virtual ratuje FPS.

Co w trawie piszczy o SWR — odświeżenie bez nadwagi

SWR (od sformułowania "stale-while-revalidate") to mniejszy, zwinny kuzyn na rynkach od zaciągania danych, za którego odpowiada z reguły potężna załoga w Vercel. Myśl o nim jak o w pełni podobnej platformie co TanStack z tym by na paczkach ucinać masowe gigabajty (na suche wyniki rzędu ~4 KB na korzyść molocha rzutowanego w rzędzie ~13 KB u starszego z rodziny frameworka dla Query).

Code
import useSWR from 'swr'
 
function UserProfile({ userId }: { userId: string }) {
  const {
    data: user,
    isLoading,
    error,
  } = useSWR(`/api/users/${userId}`, (url) => fetch(url).then((r) => r.json()))
 
  if (isLoading) return <Spinner />
  if (error) return <Error error={error} />
  return <Profile user={user} />
}

Przy bliższym spojrzeniu oba narzędzia mają dużo wspólnego, ale różnią się w kilku istotnych punktach:

Prostota – tu wygrywa SWR. Ma mniejsze API i niższy próg wejścia, więc do prostych projektów i mniejszych aplikacji jest wygodniejszym wyborem.

Bogactwo funkcji – tu wygrywa TanStack Query. Daje znacznie więcej narzędzi do złożonych aplikacji: dynamiczne klucze, rozbudowaną inwalidację cache po mutacjach, optymistyczne aktualizacje i mechanizm anulowania zapytań (query cancellation), który natychmiast przerywa fetch, gdy użytkownik np. cofnie się przed załadowaniem danych.

Typowanie TypeScript – TanStack Query ma dopracowaną integrację z TypeScriptem, z dobrą inferencją typów bez nadmiaru ręcznych adnotacji generycznych.

Reguła wyboru jest prosta: do rozbudowanych aplikacji z wieloma widokami, panelami i mutacjami (dashboardy, SaaS, e-commerce) bierz TanStack Query. Do prostszych projektów — małych paneli, stron marketingowych — wystarczy lżejszy SWR. Oba są solidne i sprawdzone w produkcji.

Jak to gra z Server Components

Złota reguła architektury w App Routerze brzmi: zanim pobierzesz dane na kliencie, zapytaj, czy w ogóle muszą tam trafić. W nowych aplikacjach Next.js większość początkowego pobierania danych powinna dziać się na serwerze, w Server Components:

Code
// app/users/[id]/page.tsx — Server Component (Next.js 15+)
async function UserPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const user = await fetchUser(id) // pobranie wykonuje się na serwerze
  return <Profile user={user} />
}

Do przeglądarki nie trafia wtedy żaden JavaScript fetchujący — serwer pobiera dane i wysyła gotowy HTML. Znikają wodospady zapytań po stronie klienta i koszt wykonania na słabszych urządzeniach. Ten sam model „zero JS domyślnie" opisuję szerzej przy okazji architektury wysp w Astro.

Czego Server Components same nie załatwią? Wszystkiego, co dzieje się interaktywnie po stronie klienta:

  • Dynamiczne filtry, paginacja i auto-uzupełnianie — gdy użytkownik klika i odświeża fragment widoku bez przeładowania strony.
  • Dane real-time — powiadomienia, czat, statystyki na żywo przez WebSockets albo SSE.
  • Mutacje danych — formularze zapisujące zmiany na serwerze, które potem aktualizują widok.

W tych przypadkach wraca TanStack Query (albo SWR) po stronie klienta.

Najlepsze z obu światów — wzorzec prehydracji

Najmocniejsze podejście łączy oba modele: Server Component pobiera dane początkowe, a kliencki TanStack Query przejmuje je jako stan startowy i dalej zarządza odświeżaniem. Użytkownik dostaje gotowy widok natychmiast (dobre dla SEO i pierwszego renderu), a interaktywność i cache działają jak w czystej aplikacji klienckiej:

Code
// app/users/[id]/page.tsx — Rzutowanie od Next.js z 15+ u rzutni z mocą od zapleczy Server Component
import { UserClient } from './user-client'
 
async function UserPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const initialUser = await fetchUser(id)
  return <UserClient userId={id} initialData={initialUser} />
}
Code
// app/users/[id]/user-client.tsx — Moduł rzucony u przeglądania u klienta "Client Component"
'use client'
 
import { useQuery } from '@tanstack/react-query'
 
export function UserClient({ userId, initialData }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    initialData,
    staleTime: 60 * 1000,
  })
 
  return <Profile user={user} />
}

Dzięki temu zyskujesz zalety obu podejść: serwer renderuje gotowy HTML pod SEO i szybki pierwszy render, a klient dostaje inteligentny cache TanStack Query do odświeżania danych na żywo.

A co z klasycznym useEffect?

Ręczny fetch w useEffect ma dziś już bardzo wąskie zastosowanie. Są w zasadzie trzy sytuacje, w których nadal bywa właściwy:

  1. Strumienie danych — WebSockets albo SSE, gdzie potrzebujesz utrzymać połączenie i subskrypcję, a nie wykonać pojedyncze pobranie. TanStack Query jest projektowany pod request-response, nie pod ciągły strumień.
  2. Jednorazowe efekty po załadowaniu — proste akcje, które nie wymagają cache, retry ani inwalidacji.
  3. Integracje z zewnętrznymi SDK — np. nasłuch zmian autoryzacji albo realtime z Firebase, gdzie podpinasz się pod cykl życia biblioteki.

Poza tymi przypadkami ręczny fetch w useEffect do pobierania danych aplikacji to antywzorzec — generuje wodospady, brak cache, brak retry i problemy z race conditions. Szczegółowo rozbieram to w artykule useEffect to prawie zawsze błąd — kiedy go naprawdę potrzebujesz, a kiedy nie.

Najczęstsze błędy z TanStack Query

W przeglądach kodu klientów te trzy pomyłki wracają najczęściej:

1. Niestabilny klucz zapytania. Jeśli do queryKey wstawisz wartość zmieniającą się przy każdym renderze (np. Date.now()), cache nigdy nie trafi — każde zapytanie ma inny klucz, więc TanStack pobiera dane od nowa za każdym razem.

Code
// ŹLE — klucz zmienia się przy każdym renderze, cache bezużyteczny
useQuery({ queryKey: ['posts', Date.now()], queryFn: ... });
 
// DOBRZE — klucz oparty na realnych parametrach zapytania
useQuery({ queryKey: ['posts', { category, page }], queryFn: ... });

2. Tworzenie QueryClient wewnątrz komponentu. Jeśli instancja QueryClient powstaje przy każdym renderze (zamiast raz, w stabilnym miejscu), cały cache resetuje się na każdej zmianie drzewa. QueryClient twórz raz — w useState/module — i przekazuj do <QueryClientProvider>.

3. Trzymanie danych serwerowych w Zustand albo Reduxie. Ręczne kopiowanie danych z serwera do globalnego store to praca, którą TanStack Query wykonuje automatycznie — z cache, inwalidacją i odświeżaniem. Globalny store zostaw na stan czysto kliencki (UI, preferencje), a dane serwerowe powierz TanStack Query. Kiedy co wybrać do zarządzania stanem, rozbieram w artykule o Context, Zustand i URL state.

Werdykt Labu

Domyślny wzorzec pobierania danych w Next.js w 2026 jest jasny: dane początkowe pobierasz w Server Components, a interaktywność i odświeżanie oddajesz TanStack Query na kliencie — najlepiej spięte wzorcem prehydracji. To łączy szybki, przyjazny SEO pierwszy render z inteligentnym cache po stronie klienta.

Resztę dobierasz do skali: SWR przy prostszych projektach, gdzie zależy Ci na minimalnym API, a useEffect do pobierania danych traktuj jako antywzorzec — zostaw go dla strumieni, integracji z SDK i jednorazowych efektów.

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.
Next.js
  • Hierarchia środowiska – ścieżka podejmowania decyzji na skróty1 min
  • Dlaczego useEffect potrafi mocno podciąć skrzydła?2 min
  • TanStack Query — Prawdziwy hegemon roku 20264 min
  • Co w trawie piszczy o SWR — odświeżenie bez nadwagi1 min
  • Jak to gra z Server Components2 min
  • A co z klasycznym useEffect?1 min
  • Najczęstsze błędy z TanStack Query1 min
  • Werdykt Labu1 min

Często zadawane pytania

Źródła i data weryfikacjiZweryfikowano: 20 maja 2026

Informacje o API, cachingu, devtools i integracji z Server Components zweryfikowano na podstawie oficjalnej dokumentacji bibliotek i frameworka:

TanStack Query docs, TanStack Query — Server Components, SWR docs, React docs — useEffect, React docs — You Might Not Need an Effect, Next.js — Data Fetching.

Seria

React w praktyce 2026
Część 3 / 4
  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. React Query (TanStack) vs SWR vs useEffect — kompletny przewodnik po fetchingu w 2026
  4. 4TypeScript w React bez bólu — 7 wzorców, które realnie robią różnicę
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Astro.js vs Next.js w 2026 — kompleksowe porównanie frameworków
Astro.js vs Next.js w 2026 — kompleksowe porównanie frameworków

Astro 6 vs Next.js 16 — zupełnie różne założenia. Które wybrać do strony usługowej, bloga, SaaS i e-commerce? Decydujące kryteria.

Maciej Sala

Maciej Sala

Founder Strivelab

15 kwietnia 2026
Backend dla frontendowca: serwer, bazy danych i API
Backend dla frontendowca: serwer, bazy danych i API

Pierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, status code, paginacja, idempotency, BFF i CORS.

Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025
Architektura wysp w Astro — czym są wyspy i dlaczego zero JS domyślnie zmienia zasady gry
Architektura wysp w Astro — czym są wyspy i dlaczego zero JS domyślnie zmienia zasady gry

Zero JS domyślnie to fundament Astro. Czym są wyspy, jak działa selektywna hydratacja i kiedy naprawdę ma to wpływ na wydajność?

Maciej Sala

Maciej Sala

Founder Strivelab

24 kwietnia 2026
Poprzedni wpisSEO w Astro — Core Web Vitals, dane uporządkowane i techniczny fundament rankingu w 2026Astro ma przewagę SEO z założenia — ale tylko jeśli wiesz, jak ją wykorzystać. Core Web Vitals, JSON-LD i GEO/AEO w jednym miejscu.
Maciej Sala

Maciej Sala

Founder Strivelab

24 kwietnia 2026
Następny wpisReact Compiler w 2026 — czy useMemo i useCallback są już martwe?React Compiler jest stabilny — ale czy naprawdę możesz teraz usunąć wszystkie useMemo i useCallback? Kiedy Compiler wyręcza Cię, a kiedy nie.
Maciej Sala

Maciej Sala

Founder Strivelab

24 kwietnia 2026