React Query Nampilin Data Lama? Penyebab dan Cara Fix (v5)
React Query nampilin data lama pas render pertama? Biasanya gara-gara initialData dianggap fresh, query key kurang lengkap, atau opsi v4 kayak keepPreviousData. Ini fix-nya di v5.

React Query nampilin stale data kalau di cache udah ada entry yang dia anggap masih fresh. Jadi entry itu langsung di-render, dan fetch-nya di-skip. Di TanStack Query v5, tiga penyebab paling sering: initialData yang digabung sama staleTime, query key yang gak memuat semua variabel request, dan opsi caching bawaan dari v4. Pindah halaman terus balik lagi sering bikin bug-nya keliatan hilang, padahal cuma karena component remount pas entry-nya udah stale.
Diagnosis Cepat
| Gejala | Kemungkinan penyebab | Fix |
|---|---|---|
| Data kosong atau default pas render pertama, bener setelah navigasi | initialData plus staleTime bikin data awal dianggap fresh | Pakai placeholderData, atau set initialDataUpdatedAt |
| Data dari user, page, atau filter sebelumnya | Query key kurang dependency | Masukin semua input queryFn ke queryKey |
| Data lama setelah mutation | Cache gak di-invalidate | invalidateQueries pakai prefix key |
| Tabel kedip kosong pas paginasi setelah upgrade ke v5 | Opsi keepPreviousData udah dihapus di v5 | placeholderData: keepPreviousData |
| Data gak refresh pas balik ke tab | staleTime kelamaan atau refetchOnWindowFocus dimatiin | Turunin staleTime atau refetch on focus |
Kenapa React Query Nampilin Data Lama di Render Pertama?
Status stale itu ditentuin per cache entry. staleTime nentuin berapa lama entry dianggap fresh, dan default-nya 0. Artinya data langsung stale begitu nyampe, kecuali lo naikin nilainya. Pas component mount, React Query cuma refetch kalau entry-nya stale. Jadi apa pun yang bikin entry salah keliatan fresh, bakal langsung di-render apa adanya.
Ini persis bug yang gue alamin di dashboard dengan paginasi. Gue isi query pakai initialData berupa object kosong biar tabelnya ada yang di-render, terus gue tambahin staleTime lima menit biar request-nya gak kebanyakan. Hasilnya, load pertama nampilin tabel kosong. Pindah halaman terus balik, baru deh data aslinya muncul.
Penyebabnya, initialData itu disimpen ke cache dan diperlakuin kayak data yang baru aja di-fetch. Dengan staleTime lima menit, React Query yakin object kosong tadi masih fresh, jadi gak ada alasan buat manggil API.
Fix 1: Pakai placeholderData Buat Loading State, Bukan initialData
placeholderData tampil selama query loading, tapi gak pernah disimpen ke cache. Jadi dia gak bisa ngeblok fetch yang asli. Flag isPlaceholderData ngasih tau lo kapan data placeholder lagi tampil.
import { useQuery } from "@tanstack/react-query";
const emptyPage = { currentPage: 0, totalItems: 0, totalPages: 0, data: [] };
const { data, isPlaceholderData } = useQuery({
queryKey: ["dataList", { userId, page, pageSize }],
queryFn: () => fetchData({ userId, page, pageSize }),
placeholderData: emptyPage,
staleTime: 1000 * 60 * 5,
});Kalau lo beneran butuh initialData, misalnya data hasil render di server, kasih tau React Query umur datanya lewat initialDataUpdatedAt. Timestamp lama, atau 0, bikin entry dianggap stale jadi query tetap refetch pas mount.
useQuery({
queryKey: ["dataList", { userId, page, pageSize }],
queryFn: () => fetchData({ userId, page, pageSize }),
initialData: serverData,
initialDataUpdatedAt: serverFetchedAt, // use 0 to always refetch on mount
staleTime: 1000 * 60 * 5,
});Fix 2: Masukin Semua Dependency ke Query Key
Query key itu alamat cache. Kalau queryFn baca userId, page, dan pageSize tapi key-nya cuma [“dataList”], semua user dan page bakal share satu cache entry. Yang tampil ya response yang paling terakhir nyampe.
// Wrong: page 2 reuses the cache entry from page 1
useQuery({ queryKey: ["dataList"], queryFn: () => fetchData({ userId, page }) });
// Right: each combination gets its own entry
useQuery({
queryKey: ["dataList", { userId, page, pageSize }],
queryFn: () => fetchData({ userId, page, pageSize }),
});ESLint plugin resmi TanStack Query punya rule exhaustive-deps buat query key yang bisa nangkep masalah ini sejak lint.
Fix 3: Ganti keepPreviousData Pakai placeholderData di v5
TanStack Query v5 udah ngehapus opsi keepPreviousData dan flag isPreviousData. Menurut migration guide resmi v5, behavior yang sama sekarang didapet dengan ngoper helper keepPreviousData ke placeholderData.
import { keepPreviousData, useQuery } from "@tanstack/react-query";
const { data, isPlaceholderData } = useQuery({
queryKey: ["dataList", { userId, page, pageSize }],
queryFn: () => fetchData({ userId, page, pageSize }),
placeholderData: keepPreviousData,
});Ada satu perubahan behavior yang penting. Dengan placeholderData, query tetap di status success selama page berikutnya loading. Sementara keepPreviousData di v4 masih bisa nunjukin status error dari query sebelumnya. Pakai isPlaceholderData buat bikin tabel agak redup atau disable tombol next page.
Fix 4: Invalidate Setelah Mutation
Mutation ngubah data di server, tapi cache gak tau soal itu. Invalidate key yang kena dampak pas mutation sukses. Di v5, invalidateQueries nerima object, dan prefix key bakal match ke semua query yang diawali prefix itu.
import { useMutation, useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
const updateItem = useMutation({
mutationFn: saveItem,
onSuccess: () => {
// Marks every ["dataList", ...] query stale and refetches the active ones
queryClient.invalidateQueries({ queryKey: ["dataList"] });
},
});Jangan panggil invalidateQueries di dalam useEffect yang jalan tiap key berubah. Key baru udah otomatis trigger fetch, jadi effect itu cuma nambah request dobel.
Beda staleTime dan gcTime Apa Sih?
staleTime ngatur kapan data perlu di-refetch. gcTime, yang namanya cacheTime sebelum v5, ngatur kapan data yang udah gak dipakai dihapus dari memory.
| Opsi | Default | Yang diatur | Naikin kalau |
|---|---|---|---|
| staleTime | 0 | Berapa lama data dianggap fresh, jadi gak refetch pas mount atau focus | Data jarang berubah dan lo mau request lebih sedikit |
| gcTime | 5 menit | Berapa lama entry yang gak dipakai tetap di cache setelah observer terakhir unmount | User sering balik ke layar yang sama |
staleTime yang tinggi gak bikin data jadi lebih fresh. Justru kebalikannya, dia nyuruh React Query percaya sama cache lebih lama.
Fix 5: Atur Kapan Refetch Terjadi
Kalau sebuah layar wajib nampilin data terbaru, paksa refetch pas mount tanpa peduli staleTime, dan refetch juga pas user balik ke tab.
useQuery({
queryKey: ["dataList", { userId, page, pageSize }],
queryFn: () => fetchData({ userId, page, pageSize }),
refetchOnMount: "always",
refetchOnWindowFocus: true,
});Buat dashboard yang harus selalu live, tambahin refetchInterval dengan nilai dalam milidetik.
Baca Juga: Zustand: Lightweight State Management for Modern React Apps
Cara Mastiin Penyebabnya Pakai React Query Devtools
Buka React Query Devtools terus pilih query-nya. Tiga detail ini udah ngejawab sebagian besar kasus stale data:
- Query key. Kalau dua layar punya key yang sama, berarti mereka share satu cache entry.
- Badge status. Badge fresh di data yang lo tau salah, itu tanda initialData plus staleTime.
- Waktu terakhir update. Timestamp sebelum halaman di-load artinya data dari cache, bukan dari request baru.
Baca Juga: Server Actions vs Client Rendering di Next.js: Panduan Developer 2025
Pertanyaan yang Sering Diajukan
Kenapa pindah halaman terus balik bisa benerin stale data di React Query?
Pas remount, pengecekan refetch on mount jalan lagi. Waktu itu entry-nya mungkin udah stale, jadi fetch terjadi. Ini cuma nutupin penyebab aslinya, yang biasanya initialData dianggap fresh atau query key kurang dependency.
Apakah default staleTime di TanStack Query v5 itu 0?
Iya. staleTime default-nya 0 dan gcTime default-nya 5 menit, jadi data dianggap stale begitu selesai di-fetch, kecuali lo atur beda.
Apa pengganti keepPreviousData di TanStack Query v5?
Import helper keepPreviousData dari @tanstack/react-query terus oper ke placeholderData. Flag isPreviousData sekarang jadi isPlaceholderData.
Apakah initialData bikin fetch pas mount?
Cuma kalau datanya stale. initialData disimpen di cache, jadi dengan staleTime lebih dari 0 dia dianggap fresh dan ngeblok fetch, kecuali initialDataUpdatedAt nunjukin datanya lebih tua dari staleTime.
Checklist Sebelum Deploy
- Loading state pakai placeholderData, bukan initialData.
- Semua nilai yang dipakai queryFn ada di queryKey.
- Mutation nge-invalidate prefix key yang kena dampak.
- Opsi v4 udah dimigrasi: cacheTime jadi gcTime, keepPreviousData jadi placeholderData.
- staleTime sesuai seberapa sering data beneran berubah.


