Episode ini membahas persistensi cache ke localStorage atau IndexedDB memakai sync-storage dan async-storage persister, serta perilaku offline: onlineManager, retry exponential backoff, dan penanganan jaringan yang terputus.

Aplikasi web modern harus tangguh di jaringan buruk. Ketika user membuka aplikasi tanpa koneksi, cache TanStack Query yang hanya ada di memori akan kosong dan halaman menampilkan loading tanpa akhir. Solusinya: persistence — menyimpan cache ke penyimpanan yang bertahan lama seperti localStorage atau IndexedDB, sehingga data tersedia saat aplikasi dibuka lagi.
Episode 12 membahas dua hal: cara menyimpan cache dengan persister, dan bagaimana TanStack Query berperilaku ketika jaringan terputus — termasuk onlineManager dan retry dengan jeda yang meningkat.
TanStack Query menyediakan dua persister resmi: query-sync-storage-persister untuk localStorage dan query-async-storage-persister untuk penyimpanan async seperti IndexedDB.
npm install @tanstack/react-query-persist-client @tanstack/query-sync-storage-persisternpm install @tanstack/react-query-persist-client menambahkan PersistQueryClientProvider, dan package persister kedua menyediakan createSyncStoragePersister.
Setelah menginstall, ganti QueryClientProvider dengan PersistQueryClientProvider:
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 1000 * 60 * 60 * 24,
},
},
})
const persister = createSyncStoragePersister({
storage: window.localStorage,
})
function App() {
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<Todos />
</PersistQueryClientProvider>
)
}createSyncStoragePersister memakai window.localStorage sebagai media penyimpanan. Catat gcTime yang diperpanjang — cache yang dipersist hanya dipulihkan jika belum melewati gcTime, jadi set gcTime lebih panjang dari sesi pendek. PersistQueryClientProvider secara otomatis menyimpan cache ke localStorage dan memulihkannya saat aplikasi dibuka.
localStorage terbatas sekitar 5 MB dan bersifat sinkron. Untuk cache yang besar, gunakan async persister dengan IndexedDB:
import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister"
import { IDBStorage } from "idb-keyval"
const persister = createAsyncStoragePersister({
storage: IDBStorage,
})createAsyncStoragePersister bekerja dengan implementasi Storage async seperti idb-keyval. IDBStorage dari idb-keyval menyimpan data secara async, lebih cocok untuk cache berukuran besar karena tidak memblokir thread utama.
Saat jaringan terputus, query yang gagal tidak langsung menyerah. TanStack Query memakai retry dengan jeda yang meningkat — exponential backoff. Setiap percobaan gagal menambah jeda sebelum percobaan berikutnya, sampai batas retry tercapai.
useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
retry: 3,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30 * 1000),
})retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30 * 1000) menghasilkan jeda 1 detik, 2 detik, 4 detik, lalu mentok di 30 detik. retryDelay menerima function untuk menghitung jeda per percobaan — pola exponential backoff bawaan tanpa library tambahan.
TanStack Query melacak status online melalui onlineManager. Saat koneksi kembali, query yang gagal dan masih dalam jangka retry akan otomatis dicoba ulang:
import { onlineManager } from "@tanstack/react-query"
onlineManager.setOnline(navigator.onLine)
window.addEventListener("offline", () => onlineManager.setOnline(false))
window.addEventListener("online", () => onlineManager.setOnline(true))onlineManager.setOnline memberi tahu library status koneksi terkini. onlineManager internal mengatur kapan retry dilanjutkan — ketika online dikembalikan ke true, permintaan yang tertunda dibangunkan kembali. Pada default, TanStack Query memakai event online/offline bawaan browser.
Untuk query yang memang tidak perlu dicoba ulang — misalnya input user yang salah — kecilkan nilai retry atau gunakan status error untuk menampilkan pesan segera. Kombinasi yang umum: retry: 1 untuk query read, dan retry default untuk operasi yang idempoten.
Warning
Data sensitif yang dipersist ke localStorage bisa terbaca oleh skrip lain di origin yang sama. Jangan persist token atau data pribadi tanpa pertimbangan keamanan — episode 15 akan membahas praktik ini lebih dalam.
Episode 12 membuat aplikasi kalian tangguh: cache yang bertahan lewat localStorage atau IndexedDB, perilaku offline yang wajar, dan retry dengan exponential backoff yang diatur oleh onlineManager. Kombinasi ini menjaga aplikasi tetap berguna meski jaringan tidak bersahabat.
Inti yang harus dibawa pulang:
PersistQueryClientProvider menyimpan cache ke media persisten.gcTime yang diperpanjang agar cache bisa dipulihkan antar sesi.onlineManager mengatur perilaku saat koneksi kembali.Di episode 13 selanjutnya kita akan membahas performance dan cache tuning — mengurangi fetch berlebih dengan staleTime yang tepat, keepPreviousData, dan normalisasi cache, plus Suspense mode dengan useSuspenseQuery untuk loading granular yang terintegrasi React Suspense.