Capítulo 51 de 80
Unlike persistQueryClient (persists the entire client as one blob), experimental_createQueryPersister persists queries individually — attach it as a query's persister option (globally via defaultOptions or per-useQuery) to choose exactly what's worth persisting, lazily restoring each query only when it's actually used.
persisterFn to queryClient's defaultOptions.queries.persister (persists everything, narrowable with filters) or to a single useQuery call (persists just that query) — each query is stored under its own key (prefix-queryHash), not bundled into one blob.queryFn run — no throttling needed since writes are naturally spaced by actual fetches, unlike persistQueryClientSave's explicit 1s throttle.staleTime still governs behavior after restore: fresh restored data doesn't refetch; stale restored data refetches immediately — same rule as any other cache hit.gcTime expiring) doesn't touch the persisted copy — this decouples memory efficiency from durability, letting gcTime stay short while data still survives across sessions via storage.queryClient.setQueryData isn't persisted: an optimistic update written directly via setQueryData is not automatically saved — a page reload before the query naturally re-runs/invalidates loses that optimistic change unless persisted manually.networkMode defaults to 'offlineFirst' when a persister is attached: since the persister sits between the query and the network as a caching layer, it needs to be able to restore even without connectivity.persisterFn: persistQueryByKey(key, queryClient) (manually persist a specific query right away — e.g. right after an optimistic setQueryData, to cover the gap described above); retrieveQuery(queryHash) (fetch a persisted query directly, auto-removing it if expired/busted/malformed); persisterGc() (sweep storage for expired/busted/malformed entries — needs the storage to expose entries()); restoreQueries(queryClient, filters) (bulk-restore matching queries up front, e.g. at app startup for instant offline-available data); removeQueries(filters) (persisted data isn't auto-removed by queryClient.removeQueries — call this too if it must actually be deleted from storage).storage (AsyncStorage-shaped, or Storage, or undefined/null for SSR); maxAge (default 24h); buster; prefix (default 'tanstack-query', forms the storage key with the query hash); refetchOnRestore (true default — refetch if stale after restore; false — never; 'always' — unconditionally); filters (a QueryFilters object narrowing which queries this persister applies to).const persister = experimental_createQueryPersister({
storage: AsyncStorage,
maxAge: 1000 * 60 * 60 * 12,
})
const queryClient = new QueryClient({
defaultOptions: { queries: { gcTime: 1000 * 30, persister: persister.persisterFn } },
})
// Covering the setQueryData gap: persist an optimistic update immediately
useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
persister.persistQueryByKey(['todos'], queryClient)
},
})
gcTime paired with a much longer persisted maxAge, and manually persisting an optimistic update that setQueryData alone wouldn't save.persistQueryClient when only some queries are worth persisting, or when per-query granularity (different maxAge, different storage) matters — it's the selective alternative.persistQueryByKey after an optimistic setQueryData write if that change must survive a reload before the mutation settles — it isn't covered automatically.queryClient.removeQueries doesn't clean up persisted storage — pair it with this persister's own removeQueries when data must actually be deleted, not just evicted from memory.offlineFirst default this persister activates.setQueryData gap this persister's persistQueryByKey addresses.