Capítulo 47 de 80
persistQueryClient dehydrates the whole QueryClient to a pluggable Persister (storage adapter) and rehydrates it on load — but gcTime must be raised to match or exceed maxAge, or the default 5-minute GC silently discards the persisted cache before it's ever used.
gcTime/maxAge trap: gcTime defaults to 5 minutes; persistQueryClient's maxAge defaults to 24 hours. If gcTime isn't raised to match, garbage collection runs long before the persisted cache's maxAge window expires, discarding data that should still be valid. Set gcTime to Infinity to disable GC entirely, or match it to maxAge — JS's setTimeout caps effective delays around 24 days, workaroundable via timeoutManager.setTimeoutProvider.buster string (e.g. a build hash) to persistQueryClient/Save/Restore — a persisted cache stored under a different buster is discarded on restore, a clean way to invalidate everything after an incompatible app change.removeClient() fires (discarding the cache) if the stored data is expired (maxAge), busted, errored, or empty.persistQueryClientSave (dehydrate + store now — sync/async persisters throttle this to at most once/second by default); persistQueryClientSubscribe (auto-save on every cache change, returns an unsubscribe); persistQueryClientRestore (hydrate from storage, discarding anything older than maxAge). persistQueryClient itself is the convenience wrapper: restore immediately, then subscribe.PersistQueryClientProvider: replaces plain QueryClientProvider; correctly subscribes/unsubscribes with the component lifecycle (a bare persistQueryClient() call outside React never unsubscribes) and holds queries in fetchingState: 'idle' until restoration finishes, avoiding a race between mounting queries and an async restore. onSuccess/onError callbacks fire after restore completes/fails — onSuccess is the place to call resumePausedMutations().useIsRestoring: reports whether a restore triggered by PersistQueryClientProvider is still in progress — useQuery and friends check this internally too, to avoid racing a fetch against an in-flight restore.Persister interface: any storage backend implements just three methods — persistClient, restoreClient, removeClient — operating on a { timestamp, buster, clientState } shape, making custom persisters (e.g. IndexedDB via idb-keyval, chosen for higher storage limits and no serialization requirement) straightforward to build.const queryClient = new QueryClient({
defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24 } }, // must be >= maxAge
})
const persister = createAsyncStoragePersister({ storage: window.localStorage })
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
<App />
</PersistQueryClientProvider>
gcTime to match the default maxAge, and wiring persistence through the React-safe provider.gcTime to at least maxAge (or Infinity) whenever persistence is enabled — this is the single most common way persisted caches silently "don't work."PersistQueryClientProvider over calling persistQueryClient() directly outside React — the raw function has no lifecycle-tied unsubscribe and races against initial render.Persister is a three-method interface — reach for it (e.g. IndexedDB) when localStorage's size limits or serialization requirements are a problem.gcTime and resumePausedMutations, both central here.PersistQueryClientProvider's onSuccess.