Web Development

React Query Nampilin Data Lama? Penyebab dan Cara Fix (v5)

Asep Alazhari

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 Data Lama? Penyebab dan Cara Fix (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

GejalaKemungkinan penyebabFix
Data kosong atau default pas render pertama, bener setelah navigasiinitialData plus staleTime bikin data awal dianggap freshPakai placeholderData, atau set initialDataUpdatedAt
Data dari user, page, atau filter sebelumnyaQuery key kurang dependencyMasukin semua input queryFn ke queryKey
Data lama setelah mutationCache gak di-invalidateinvalidateQueries pakai prefix key
Tabel kedip kosong pas paginasi setelah upgrade ke v5Opsi keepPreviousData udah dihapus di v5placeholderData: keepPreviousData
Data gak refresh pas balik ke tabstaleTime kelamaan atau refetchOnWindowFocus dimatiinTurunin 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.

OpsiDefaultYang diaturNaikin kalau
staleTime0Berapa lama data dianggap fresh, jadi gak refetch pas mount atau focusData jarang berubah dan lo mau request lebih sedikit
gcTime5 menitBerapa lama entry yang gak dipakai tetap di cache setelah observer terakhir unmountUser 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:

  1. Query key. Kalau dua layar punya key yang sama, berarti mereka share satu cache entry.
  2. Badge status. Badge fresh di data yang lo tau salah, itu tanda initialData plus staleTime.
  3. 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.
Back to Blog

Related Posts

View All Posts »