T
TanStack7mo ago
generous-apricot

Cached local storage query persist

I am fetching relatively static data from an API that i dont want to spam but may update occasionally. i never watch to refetch within a particular session and i only want to fetch upon page load, but also not more often than X hours. if i just visit the page for the first time, it should fetch. but then if i refresh the page a minute later, it should not refetch and just load from cache instead. if i wait say 12 hours and refresh, then it should fetch again and update the local storage. I have the following basic code and the above functionality is not working. The issue is that it refetches upon page load every time. my understanding is that the config i pass to QueryClient default options prevents all reloading during a session, and the persistOptions passed to PersistQueryClientProvider should ensure that it loads from cache until maxAge has passed upon which itll refetch from the API. What am i missing here?
const localStoragePersister = createSyncStoragePersister({
storage: window.localStorage,
})

const queryClient = new QueryClient({
defaultOptions: {
queries: {
// never refetch data during a particular session
staleTime: Infinity,
gcTime: Infinity,
retry: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
},
},
})

// Render the app
const rootElement = document.getElementById('app')
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<PersistQueryClientProvider
client={queryClient}
// only refetch data upon page load if the data is old enough
persistOptions={{
persister: localStoragePersister,
maxAge: 3600,
}}
>
<RouterProvider router={router} />
</PersistQueryClientProvider>
</StrictMode>,
)
}
const localStoragePersister = createSyncStoragePersister({
storage: window.localStorage,
})

const queryClient = new QueryClient({
defaultOptions: {
queries: {
// never refetch data during a particular session
staleTime: Infinity,
gcTime: Infinity,
retry: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
},
},
})

// Render the app
const rootElement = document.getElementById('app')
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<PersistQueryClientProvider
client={queryClient}
// only refetch data upon page load if the data is old enough
persistOptions={{
persister: localStoragePersister,
maxAge: 3600,
}}
>
<RouterProvider router={router} />
</PersistQueryClientProvider>
</StrictMode>,
)
}
9 Replies
fascinating-indigo
fascinating-indigo7mo ago
Likely has to do with the fact that the persistent storage is not the source of truth, the cache used by your QueryClient is (which by default is a Map stored in memory afaik). I am quite sure the persisted storage is only used the first time your app loads, to hydrate the QueryClient's cache, then the persisted store is only updated with what is in the actual cache set a proper gcTime instead I am not sure if the above is true, but that is my guess
generous-apricot
generous-apricotOP7mo ago
not sure i understand what youre saying. i want the persisted storage to be used upon page load so that queryclient doesnt go and request the api
fascinating-indigo
fascinating-indigo7mo ago
what do you mean by "page load" is that the initial browser download/load? or are you using some kind of router library? (nvm, I just saw the RouterProvider of, my guess, react-router, but I still need to know what you mean by "page load")
generous-apricot
generous-apricotOP7mo ago
when the user refreshes or visits the page from a different url i get what you mean by app load, that navigating between different pages in my app wont trigger reload of local storage since the router is handling that its not too important, the key thing is that i dont want to re call the api too often
fascinating-indigo
fascinating-indigo7mo ago
First of all, there is a mistake in your code, i think, you set maxAge to 3600, but maxAge is specified in milliseconds, doubt you want to discard your cache if older than 3.6 seconds second of all, you'd still want a gcTime on your queries, if you want queries to be cached for 12 hours, you set gcTime to 1000 * 60 * 60 * 12 the gcTime is for the case in which the user keeps his tab loaded in his browser for more than 12 hours in your case, you might want maxAge to be the same as gcTime
generous-apricot
generous-apricotOP7mo ago
ok yeh im an idiot, it was the milliseconds lmao and fair point on the gctime, ill set that too, thanks
fascinating-indigo
fascinating-indigo7mo ago
It also hurts my brain when thinking about these properties, anyway, some takeaways: - Persisted data is only loaded once, the first time your react app loads, that's when maxAge is checked and, if the cache is old, it is discarded so you start with an empty cache; - From there on, the persisted data gets updated, likely, on every actual cache change, with the cache's data; - staleTime defines after how much time the data is considered stale, tbh I would also need some clarification on this; - gcTime defines how long the data remains in the cache after all hooks that were watching that queryKey unmounted, if you have a hook mounted for a query, then it will not remove the data from the cache until the hook unmounts and the gcTime passes; Sources, docs for useQuery and: https://tanstack.com/query/latest/docs/framework/react/guides/caching#basic-example
fascinating-indigo
fascinating-indigo7mo ago
There is also this part, which explains what happens with "stale" queries
No description

Did you find this page helpful?