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: {
const localStoragePersistor = createWebStoragePersistor({
storage: window.localStorage,
const doNotPersistQueries: QueryKey[] = [paddleQueryKey]persistQueryClient({
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(
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),
return (
<RemoteStateContext.Provider value={{ state: data, updateState }}>

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