React-query Cache in Local Storage with persistQueryClient

🚨Watch On YouTube

We can immediately load the app state by saving the react-query cache to local storage. Let's try it out!

react-query has a utility for persisting the state of your queryClient and its caches for later use.

We can import everything from react-query. For persist to work properly, we need to pass QueryClient a cache time value to override the default during hydration. It should be at least 24 hours. Let’s set it to 5 days. To exclude some queries from being persistent, we can pass a filter function to the shouldDehydrateQuery param. To make react-query work, we'll pass the client to the QueryClientProvider.

import { paddleQueryKey } from "membership/paddle/hooks/usePaddleSdk"
import { QueryClient, QueryKey } from "react-query"
import { createWebStoragePersistor } from "react-query/createWebStoragePersistor-experimental"
import { persistQueryClient } from "react-query/persistQueryClient-experimental"
import { MS_IN_DAY } from "utils/time"
const cacheTime = MS_IN_DAY * 5export const queryClient = new QueryClient({
defaultOptions: {
queries: {
cacheTime,
},
},
})
const localStoragePersistor = createWebStoragePersistor({
storage: window.localStorage,
})
const doNotPersistQueries: QueryKey[] = [paddleQueryKey]persistQueryClient({
queryClient,
persistor: localStoragePersistor,
maxAge: cacheTime,
hydrateOptions: {},
dehydrateOptions: {
shouldDehydrateQuery: ({ queryKey }) => {
return !doNotPersistQueries.includes(queryKey)
},
},
})

I load everything the user needs with one query — userStateQuery. It's a productivity app, and there is not that much data coming from the back-end. I use the graphQL query string as a key for react-query to force cache invalidation on change. The query is enabled only for a logged-in user. The query function makes a post request to the GraphQL API.

const userStateQuery = `
query userState($input: UserStateInput!) {
userState(input: $input) {
...
}
}
`
const remoteStateQueryKey = userStateQueryinterface Props {
children: ReactNode
}
export const RemoteStateProvider = ({ children }: Props) => {
const isLoggedIn = useIsUserLoggedIn()
const queryClient = useQueryClient() const dispatch = useDispatch() const { data = null } = useQuery(
remoteStateQueryKey,
async () => {
const remoteState: RemoteStateView = await postToMainApi({
query: userStateQuery,
variables: {
input: {
timeZone: offsetedUtils.getOffset(),
},
},
})
return remoteState
},
{
keepPreviousData: true,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: Infinity,
enabled: isLoggedIn,
}
)
const updateState = useCallback(
(pieceOfState: Partial<RemoteStateView>) => {
queryClient.setQueryData<RemoteStateView>(remoteStateQueryKey, state => ({
...((state || {}) as RemoteStateView),
...pieceOfState,
}))
},
[queryClient]
)
return (
<RemoteStateContext.Provider value={{ state: data, updateState }}>
{children}
</RemoteStateContext.Provider>
)
}

When the user signs out from the app, I clear all the cache.

Now, when we reload the app, there’s no waiting, everything loads immediately!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store