You're using Apollo Client useQuery
for fetchPolicy
'cache-and-network' that checks the cache before refetching in the background, but you still always get a loading spinner showing until the network query returns.
This is a wrapper around useQuery
that returns loading
as false if you have a cache.
Now the loading
should work as you expect, your UI will not show a loading spinner if there is cache and then when the network query completes Apollo will re-render the component for you.
From the Apollo docs - Setting a fetch policy, replace:
const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: 'cache-and-network',
// other options
});
if (loading) {
return (...)
}
with:
const { loading, error, data } = useCacheNetworkQuery(GET_DOGS, {
// other options
});
if (loading) {
return (...)
}
That's it, useQuery -> useCacheNetworkQuery
and remove fetchPolicy
option.
Apollo's useQuery
defaults to only ever checking the cache if it exists. This is via the fetchPolicy
that is 'cache-first'.
In my opinion, if you have a normal size app (tens not hundreds of queries), then dealing with the impact of a fetchPolicy
of 'cache-first' for a list of items has a massive overhead. You have to:
- Handle cache from add and delete mutations
- Handle any cached queries using variables for add and delete mutations
- Handle any server side changes
You have to add this boilerplate for every single one of your list item queries.
You can do all that or just add this to your useQuery
options:
{
fetchPolicy: 'cache-and-network'
}
As per the Apollo docs:
Apollo Client executes the full query against both the cache and your GraphQL server. The query automatically updates if the result of the server-side query modifies cached fields.
Provides a fast response while also helping to keep cached data consistent with server data.
The problem with this is that if you have a loading spinner, the query sets loading
to true even though it is fetching directly from the cache. So if you have
if (loading) {
return (...)
}
then the loading spinner will show, whilst the network fetch is happening even though useQuery
fetches immediately from the cache.
This makes the UX as if you had { fetchPolicy: 'network-only' }
and you completely lose the 'fast response' suggested by the docs.
This hook returns loading
true only if there is no cache. Then Apollo handles the re-render once the network request updates the cache.
This problem is perfectly explained in apollographql/apollo-client #8669, with the code for this from a workaround comment by @puglyfe. The impact of the very simple switch, turns a completely non-reactive app that shows a loading spinner on every page load, to being the reactive app you expect, that loads once and then is instantaneous.
There's practically no code here, but it was such a jaw dropping moment for me to finally see React + Apollo cache working correctly that I wish Apollo did this by default. There is a reasonable explantion in the issue above why they don't, but I think its worth spreading this to get my sanity back for dealing with Apollo caching.