May 30, 2022
3 min read
We can immediately load 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 clietn 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 * 5
export 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 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 = userStateQuery
interface 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!