Data in the cache represents the accumulated, often overlapping contributions of multiple queries, so it doesn't make sense to delete a query from the cache, because you might be deleting data that other queries are depending on.
However, you can evict objects by their IDs with cache.evict({ id })
, and you can remove root query fields that were used by a given query using cache.evict({ fieldName: <the field name> })
. If you run cache.gc()
after evicting data, the cache can automatically clean up any data that is no longer reachable from other objects in the cache. See the Garbage collection and cache eviction docs for more details.
If I have cache
ROOT_QUERY { SOME_QUERY[Product]: .... ANOTHER_QUERY[Product]:... }
I read the articles that update cache using
readQuery
,writeQuery
But what I want to is, removing whole query itself. It like removing key:val from object.
How to delete whole cache query?
This blog might help, see the Eviction API. It has a good example unlike the Apollo Docs 馃檮
Thanks to @danReynolds for his amazing blog.
https://danreynolds.ca/tech/2020/05/04/Apollo-3-Client-Cache/
We had a lot of queries we were running & got curious about how we could clean up better after ourselves & make debugging easier. This ticket shows up right away when we went to search. @benjamn started with a good hint, & the blog @abhagsain documents this quite well! It was still a bit to digest but good. It comes down to evicting a field from the root query object, like so:
cache.evict({ id: "ROOT_QUERY", fieldName: "employees" });
to delete all employees queries.cache.evict({ id: "ROOT_QUERY", fieldName: "employees", args: { country: "US" }});
to delete one query with specific args.id
defaults to "ROOT_QUERY"
& can therefore be omitted. :wink: For what it's worth, our team would love to have an easier way to do this. We are big fans of the React Hooks. Perhaps useQuery results could return an evict
function that runs this above ROOT_QUERY evict?
@benjamn @abhagsain @rektide I've read @danReynolds article. Trying the same things to evict my countries query result. Couldn't remove it from the cache. You can seet it here sandbox
What am I missing?
Thanks & regards
@benjamn @abhagsain @rektide I've read @danReynolds article. Trying the same things to evict my countries query result. Couldn't remove it from the cache. You can seet it here sandbox
What am I missing?
Thanks & regards
Hey @killjoy2013 You're using a newer version of AC (3.2.5). I downgraded to AC 3.0.0 and eviction working fine (IDK Why evict isn't working in the newer version need to see the changelogs) but for some reason Refetching again returns no data from the server. No Clue :\
Checkout this sandbox
@benjamn @abhagsain @rektide I've read @danReynolds article. Trying the same things to evict my countries query result. Couldn't remove it from the cache. You can seet it here sandbox
What am I missing?
Thanks & regards
I gave it a quick look and the eviction does work, if you put a debugger after the evict
call you can see that it is indeed removed. You just don't see a change because since the returnPartialData
option defaults to false, you will only get data updates if your query can be satisfied by the cache. Since it cannot after the eviction, you don't receive new data until it comes back from the network, which you can see happens if you look at the network tab after the eviction call. Since your data is the same from that call, it doesn't bust cache memoization and so you never get a re-render. If you instead change your query to have the option returnPartialData: true
you'll see the re-renders happen or if you have your query return different results you'll see it change as well.
hope that makes sense! Happy to chat more.
@benjamn @abhagsain @rektide I've read @danReynolds article. Trying the same things to evict my countries query result. Couldn't remove it from the cache. You can seet it here sandbox
What am I missing?
Thanks & regardsHey @killjoy2013 You're using a newer version of AC (3.2.5). I downgraded to AC 3.0.0 and eviction working fine (IDK Why evict isn't working in the newer version need to see the changelogs) but for some reason Refetching again returns no data from the server. No Clue :\
Checkout this sandbox
Hey @abhagsain, tried your sandbox. It clears the cache nicely. However, hitting query button again don't get the results again. Can you please give it a try?
Thanks
@benjamn @abhagsain @rektide I've read @danReynolds article. Trying the same things to evict my countries query result. Couldn't remove it from the cache. You can seet it here sandbox
What am I missing?
Thanks & regardsI gave it a quick look and the eviction does work, if you put a debugger after the
evict
call you can see that it is indeed removed. You just don't see a change because since thereturnPartialData
option defaults to false, you will only get data updates if your query can be satisfied by the cache. Since it cannot after the eviction, you don't receive new data until it comes back from the network, which you can see happens if you look at the network tab after the eviction call. Since your data is the same from that call, it doesn't bust cache memoization and so you never get a re-render. If you instead change your query to have the optionreturnPartialData: true
you'll see the re-renders happen or if you have your query return different results you'll see it change as well.hope that makes sense! Happy to chat more.
Hey @danReynolds, thank you for the explaination. Tried your suggestion returnPartialData: true
. Howevere hitting Query
couldn't obtain data sandbox. Should I be usin it differently?
@danReynolds @benjamn @abhagsain I removed the useLazyQuery
hook and make the same use case just using client.query
. This way we completely isolated the render effect from useLazyQuery
hook. sandbox It works correctly and no undesired network call happens after cache.evict({
id: "ROOT_QUERY",
fieldName: "countries"
});
Of course this is not as elegant as using useLazyQuery
:-)
@danReynolds, if I got you correctly, returnPartialData: true
parameter is supposed to be used to control this render triggered by the useLazyQuery
hook, right? As I mentioned previously, returnPartialData: true
caused problem in useLazyQuery
. If we can get it over, we can use the elegant hook solution...
Thanks & regards
@danReynolds @benjamn @abhagsain I removed the
useLazyQuery
hook and make the same use case just usingclient.query
. This way we completely isolated the render effect fromuseLazyQuery
hook. sandbox It works correctly and no undesired network call happens aftercache.evict({ id: "ROOT_QUERY", fieldName: "countries" });
Of course this is not as elegant as using
useLazyQuery
:-)
@danReynolds, if I got you correctly,returnPartialData: true
parameter is supposed to be used to control this render triggered by theuseLazyQuery
hook, right? As I mentioned previously,returnPartialData: true
caused problem inuseLazyQuery
. If we can get it over, we can use the elegant hook solution...
Thanks & regards
Oh if your goal is to evict without causing a network request then you can use the lazy query and a fetch policy of 'standby' which will get the data but never update. You can also accomplish this by using a network-only policy for first render and then switch to cache-only after you get data
@danReynolds @benjamn @abhagsain I removed the
useLazyQuery
hook and make the same use case just usingclient.query
. This way we completely isolated the render effect fromuseLazyQuery
hook. sandbox It works correctly and no undesired network call happens aftercache.evict({ id: "ROOT_QUERY", fieldName: "countries" });
Of course this is not as elegant as usinguseLazyQuery
:-)
@danReynolds, if I got you correctly,returnPartialData: true
parameter is supposed to be used to control this render triggered by theuseLazyQuery
hook, right? As I mentioned previously,returnPartialData: true
caused problem inuseLazyQuery
. If we can get it over, we can use the elegant hook solution...
Thanks & regardsOh if your goal is to evict without causing a network request then you can use the lazy query and a fetch policy of 'standby' which will get the data but never update. You can also accomplish this by using a network-only policy for first render and then switch to cache-only after you get data
Actually, in my real use case, countries are selectable as well. To do this, I'm using type policies and reactive variables sandbox. For this senario, useLazyQuery hook should be running the way it already is. Tweaking useLazyQuery can cause other issues and a simple senario can become unnecessarily complicated. Instead, if we can somehow make cache.evict({ id: "ROOT_QUERY", fieldName: "countries" })
satisfy the query and the cache without a network call, I believe, it would be the most optimum choice. Maybe we can do it by adding an optional parameter to evict call.
Does that make sense?
@danReynolds There is a broadcast parameter in cache.evict !
using like this completely solved it;
cache.evict({
id: "ROOT_QUERY",
fieldName: "countries",
broadcast: false
});
I am developing a offline first app and therefore I need to constantly read/write queries offline and cleanup my cache. I basically have to run the all logic on the front end. The mutation barely return anything (some timestamp for conflict resolution).
That being said I am struggling to have an easy way to remove unreachable and dangling reference.
Using gc()
does not seem to work at all after all my tests. None of the unreachable normalized items are deleted.
Here is how I do it right now. Let's take this use case I have:
I have a list of series and I want to remove one. The series needs to be removed from the list together with its details
What I will end up doing is:
const normalizedId = client.cache.identify({ id, __typename: 'Series' })
const normalizedId = client.cache.identify({ id, __typename: 'Series' })
if (normalizedId) {
// Remove the normalized item
client.cache.evict({ id: normalizedId })
// From this point we have dangling everywhere else
// We remove the query about that specific series
client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'oneSeries', args: { id } })
// We remove the series from the list of series
const data = client.readQuery({ query: QuerySeries })
data && client.writeQuery({ query: QuerySeries, data: { series: data?.series?.filter(item => item?.id !== id) } })
}
I have basically
query oneSeries { id ... }
query Series { id ... }
You can see that I need three step in order to clean up everything. Since the app is offline first I really need to clean up my mess. So yeah this was more of a feedback than trying to find a solution. At the moment I have not found an easier way to deal with my local state.
Also I do not like dealing with the cache directly, I would rather only use the client and its abstraction. Using client.cache.evict feels a bit embarassing to me. Especially since I do not have the types with the cache methods (I do with the client and graphql-gen)
What I would like to eventually have is one easy way from apollo to says remove every trace of this normalized item
. Since it could be CPU intensive with huge local state, it could be something that takes a list of queries to clean up. The point is that you don't need to do it yourself. Something like
client.evict({ id, queries: [QuerySeries, QueryOneSeries] })
which basically contains all of what I am doing manually right now.
@danReynolds @benjamn @abhagsain I removed the
useLazyQuery
hook and make the same use case just usingclient.query
. This way we completely isolated the render effect fromuseLazyQuery
hook. sandbox It works correctly and no undesired network call happens aftercache.evict({ id: "ROOT_QUERY", fieldName: "countries" });
Of course this is not as elegant as usinguseLazyQuery
:-)
@danReynolds, if I got you correctly,returnPartialData: true
parameter is supposed to be used to control this render triggered by theuseLazyQuery
hook, right? As I mentioned previously,returnPartialData: true
caused problem inuseLazyQuery
. If we can get it over, we can use the elegant hook solution...
Thanks & regardsOh if your goal is to evict without causing a network request then you can use the lazy query and a fetch policy of 'standby' which will get the data but never update. You can also accomplish this by using a network-only policy for first render and then switch to cache-only after you get data
Hey @danReynolds, we need to set the fetch-policy of a query hook while we create it. Is it possible to change the fetch-policy later on? So as to be able to run the same query hook with different fetch-policies depending on the situation?
added nextFetchPolicy: "no-cache" to useQuery can remove the query from cache with cache.evict. but the ui doesn't update
nextFetchPolicy
@raymclee it's awesome! That's what I've been looking for. There's almost nothing about nextFetchPolicy
in apollo client docs. Does it have to be that hard to find such things in Apollo docs?
Thanks & regards
I am developing a offline first app and therefore I need to constantly read/write queries offline and cleanup my cache. I basically have to run the all logic on the front end. The mutation barely return anything (some timestamp for conflict resolution).
That being said I am struggling to have an easy way to remove unreachable and dangling reference.
Using
gc()
does not seem to work at all after all my tests. None of the unreachable normalized items are deleted.Here is how I do it right now. Let's take this use case I have:
I have a list of series and I want to remove one. The series needs to be removed from the list together with its details
What I will end up doing is:
const normalizedId = client.cache.identify({ id, __typename: 'Series' }) const normalizedId = client.cache.identify({ id, __typename: 'Series' }) if (normalizedId) { // Remove the normalized item client.cache.evict({ id: normalizedId }) // From this point we have dangling everywhere else // We remove the query about that specific series client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'oneSeries', args: { id } }) // We remove the series from the list of series const data = client.readQuery({ query: QuerySeries }) data && client.writeQuery({ query: QuerySeries, data: { series: data?.series?.filter(item => item?.id !== id) } }) }
I have basically
One normalized series
One query for one series
query oneSeries { id ... }
One query for list of series
query Series { id ... }
You can see that I need three step in order to clean up everything. Since the app is offline first I really need to clean up my mess. So yeah this was more of a feedback than trying to find a solution. At the moment I have not found an easier way to deal with my local state.
Also I do not like dealing with the cache directly, I would rather only use the client and its abstraction. Using client.cache.evict feels a bit embarassing to me. Especially since I do not have the types with the cache methods (I do with the client and graphql-gen)
What I would like to eventually have is one easy way from apollo to says
remove every trace of this normalized item
. Since it could be CPU intensive with huge local state, it could be something that takes a list of queries to clean up. The point is that you don't need to do it yourself. Something like
client.evict({ id, queries: [QuerySeries, QueryOneSeries] })
which basically contains all of what I am doing manually right now.
If it helps I wrote https://github.com/NerdWalletOSS/apollo-invalidation-policies to make eviction across multiple queries somewhat more organized, let me know if there's a feature that would make it easier for you
Most helpful comment
This blog might help, see the Eviction API. It has a good example unlike the Apollo Docs 馃檮
Thanks to @danReynolds for his amazing blog.
https://danreynolds.ca/tech/2020/05/04/Apollo-3-Client-Cache/