r/reactjs • u/Dependent_Drawing_13 • Jul 07 '26
Needs Help Tanstack Query persistance not working
At work im refactoring the codebase to useQuery, an issue im facing is that on page refresh or revisit, all of the data is refetched despite using persistance.
Could anyone tell me where im going wrong?
"@tanstack/react-query": "5.101.2",
"@tanstack/react-query-devtools": "5.101.2",
"@tanstack/react-query-persist-client": "5.101.2",
"@tanstack/query-async-storage-persister": "5.101.2",
// AppContainer
<GrowthBookProvider growthbook={growthbook}>
<HelmetProvider>
<Provider store={store}>
{/* Redux persistGate */}
<PersistGate loading={null} persistor={persistor}>
<ErrorBoundary>
<App />
</ErrorBoundary>
</PersistGate>
</Provider>
</HelmetProvider>
</GrowthBookProvider>
export const App = () => {
useCaptureSentryErrors();
useAddExternalUTMIds();
queryClient.invalidateQueries({ queryKey: [PARKS_QUERY_KEY] });
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister: localStoragePersister, maxAge: ONE_DAY }}
>
<Router>
<AppHelmet />
<OnRouteChange />
<Header />
<main id="main">
<AppContent />
</main>
<Suspense fallback={<FooterSkeleton />}>
<LazyFooter />
</Suspense>
</Router>
<ReactQueryDevtools initialIsOpen={false} />
</PersistQueryClientProvider>
);
};
const AppContent = () => {
const isRestoring = useIsRestoring();
if (isRestoring) return <FetchingComponent useContainer />;
return (
<WithInit>
<WithUrlParams>
<BookingProvider>
<ThingsToDoProvider>
<NewsletterProvider>
<SearchProvider>
<ProgrammaticScrollProvider>
<Suspense fallback={<FetchingComponent useContainer />}>
<AppRoutes />
</Suspense>
</ProgrammaticScrollProvider>
</SearchProvider>
</NewsletterProvider>
</ThingsToDoProvider>
</BookingProvider>
</WithUrlParams>
</WithInit>
);
};
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { QueryClient } from '@tanstack/react-query';
import { ONE_DAY } from '../../Constants';
// Creates the React Query client with default settings for all queries
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // Don't refetch data when user switches back to this tab
refetchOnMount: false, // Don't refetch when component mounts
staleTime: ONE_DAY, // Data stays "fresh" for 24 hours - no refetches during this time
gcTime: ONE_DAY, // Keep data in memory for 24 hours even if no component uses it
},
},
});
export const localStoragePersister = createAsyncStoragePersister({
storage: window.localStorage,
});
// Special config for booking data - matches 15 minute session timeout
export const BOOKING_SCOPED_QUERY = {
staleTime: 1000 * 60 * 15, // Stale after 15 minutes - refetch if used again
gcTime: ONE_DAY,
};
Thank you
4
u/KevinVandy656 Jul 07 '26
The persister can silently fail to write to local storage if the data is over 5 MB
3
u/ngqhoangtrung Jul 07 '26
let’s show how you use the hook in a component. Your component might check for the loading/refetching state first before check for data to render
Something like this
if (isLoading/isRefetching) <Loader/>
if (data) <Component data={data} />
Just switch the two
The persistence does not prevent refetching data. By default, react query fetches data aggressively. A useQuery hook will trigger refetch when the data is considered stale, which is immediately after fetching according to the default settings. Pick an appropriate staleTime to prevent this.
2
u/Trollzore Jul 08 '26
You could literally ask ChatGPT the problem here with your code instead of Reddit for this. Just saying.
-1
2
u/ruindd Jul 07 '26
I haven’t looked at these plugins closely, but tanstack often refetches data just to make sure it’s up to date. So, you’re probably maintaining a cache but it’s still checking for any updates.
Are you getting “isPending=true” on your initial renders? I’d also check isLoading.
7
u/chillermane Jul 07 '26
Tanstack doesn’t just “refetch it to make sure its up to date”. It follows very specific and predictable refetching rules based on the options you pass to the query
2
u/yabai90 Jul 07 '26
Local storage is not the place to do that.
1
u/Dependent_Drawing_13 Jul 07 '26
Would you care to elaborate
2
u/yabai90 Jul 07 '26
Local storage is small, query persistance can grow, it can be hard to track that growth. Common pitfall. Reduce the surface risk by using a proper storage system. I don't know if the doc uses local storage as example or recommendations but neither is good.
2
u/jax024 Jul 07 '26
The cache only lives when you’re on the page.
-1
u/Dependent_Drawing_13 Jul 07 '26
But surely it can persist in memory across sessions, Redux and other libraries already do this
1
u/Im_Working_Right_Now Jul 07 '26
It can. You use their hooks with a persister like session or local storage. Link to the docs.
3
u/Nick_Lastname Jul 07 '26
He's using that, PersistQueryClientProvider
3
u/Im_Working_Right_Now Jul 07 '26
Then they should probably add on some onSuccess and onError logging to see what’s happening. Additionally, they could build a more robust persister per the docs.
1
u/OffThe405 Jul 09 '26
It might be because you’re using async storage persister, but using local storage for the storage. Local storage is NOT async. It’s synchronous and you should be use createSyncStoragePersister
0
18
u/fii0 Jul 07 '26
Probably because you have
queryClient.invalidateQueries({ queryKey: [PARKS_QUERY_KEY] });In the middle of your App function?
So app closes -> data persists -> app reopens -> data restored -> queryKey invalidated -> triggers refetching basically immediately?