Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to delete cache query? #6795

Closed
bexoss opened this issue Aug 7, 2020 · 23 comments
Closed

Is there a way to delete cache query? #6795

bexoss opened this issue Aug 7, 2020 · 23 comments
Assignees

Comments

@bexoss
Copy link

bexoss commented Aug 7, 2020

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?

@benjamn
Copy link
Member

benjamn commented Aug 7, 2020

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.

@abhagsain
Copy link

abhagsain commented Sep 16, 2020

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/

@rektide
Copy link

rektide commented Oct 20, 2020

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.
  • PS, id defaults to "ROOT_QUERY" & can therefore be omitted. 😉

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?

@killjoy2013
Copy link

@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

@abhagsain
Copy link

@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

@danReynolds
Copy link
Contributor

danReynolds commented Nov 1, 2020

@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.

@killjoy2013
Copy link

@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

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

@killjoy2013
Copy link

@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.

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?

@killjoy2013
Copy link

@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
Copy link
Contributor

danReynolds commented Nov 2, 2020

@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

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

@killjoy2013
Copy link

@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

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

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?

@killjoy2013
Copy link

@danReynolds There is a broadcast parameter in cache.evict !

using like this completely solved it;

          cache.evict({
              id: "ROOT_QUERY",
              fieldName: "countries",
              broadcast: false
            });

sandbox

@mbret
Copy link

mbret commented Nov 13, 2020

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.

@killjoy2013
Copy link

@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

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

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?

@raymclee
Copy link

raymclee commented Nov 25, 2020

added nextFetchPolicy: "no-cache" to useQuery can remove the query from cache with cache.evict. but the ui doesn't update

@killjoy2013
Copy link

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

@danReynolds
Copy link
Contributor

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

@danieldauk
Copy link

danieldauk commented Mar 7, 2021

Is there a way to get fieldName in a type-safe manner? Or delete cache using query DocumentNode?

@davidgilbertson
Copy link

davidgilbertson commented Mar 10, 2021

@benjamn you said "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".

I have the following scenario:

  • A page showing a list of items, with data fetched via an items query
  • A page showing an individual item, with data fetch via an item(id) query
  • A user can visit an item, putting that query in cache
  • A user can then navigate to the page with the list of items and delete an item.

At this point, wouldn't you agree that it makes perfect sense to delete the query that fetches the individual item I just deleted? At the moment, when I try to update the cache, it triggers that query to run again and of course I get an error from the server because that item doesn't exist anymore.

(Edit: I worked out that Apollo doesn't work correctly with <React.StrictMode> so maybe that is the root of my problems/confusion.)

I'm struggling to work out the 'correct' way to do this sort of thing (for this case and the case where I have items and sub-items). Is it cache.evict() with cache.gc() or cache.modify() or cache.writeQuery()? Some combination of these?

It would be amazing to get one more example in the docs, next to https://www.apollographql.com/docs/react/caching/cache-interaction/#example-removing-an-item-from-a-list, that showed this surely pretty-common requirement.

aayush-k added a commit to aayush-k/apollo-client that referenced this issue May 5, 2021
…or cache.evict(options)

Tried this without looking at which options were required/not required and realized that just providing a fieldName is sufficient when i want to evict all cached entries for a specific gql query. I also found that @benjamn mentioned the same solution/implied that `id` is optional in apollographql#6795 (comment)
@davidmillercode
Copy link

@benjamn you said "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".

I have the following scenario:

  • A page showing a list of items, with data fetched via an items query
  • A page showing an individual item, with data fetch via an item(id) query
  • A user can visit an item, putting that query in cache
  • A user can then navigate to the page with the list of items and delete an item.

At this point, wouldn't you agree that it makes perfect sense to delete the query that fetches the individual item I just deleted? At the moment, when I try to update the cache, it triggers that query to run again and of course I get an error from the server because that item doesn't exist anymore.

(Edit: I worked out that Apollo doesn't work correctly with <React.StrictMode> so maybe that is the root of my problems/confusion.)

I'm struggling to work out the 'correct' way to do this sort of thing (for this case and the case where I have items and sub-items). Is it cache.evict() with cache.gc() or cache.modify() or cache.writeQuery()? Some combination of these?

It would be amazing to get one more example in the docs, next to https://www.apollographql.com/docs/react/caching/cache-interaction/#example-removing-an-item-from-a-list, that showed this surely pretty-common requirement.

Hey @davidgilbertson did you ever figure out a clean way to do this? My team is also running into this issue and haven't found any nice ways to solve this issue.

@davidgilbertson
Copy link

@davidmillercode I don't quite recall what I did to resolve this (I've moved on from the project), sorry.

brainkim pushed a commit that referenced this issue Aug 5, 2021
…or cache.evict(options)

Tried this without looking at which options were required/not required and realized that just providing a fieldName is sufficient when i want to evict all cached entries for a specific gql query. I also found that @benjamn mentioned the same solution/implied that `id` is optional in #6795 (comment)
@chrismllr
Copy link

We were able to utilize the onQueryUpdated() API to return false; for queries that did not need to be refetched after the cache.evict:
https://www.apollographql.com/docs/react/data/mutations/#refetching-after-update

@MrDoomBringer MrDoomBringer self-assigned this Jun 29, 2022
@MrDoomBringer
Copy link
Contributor

The docs have been updated for this issue in #8146, so it looks like this should be good to be closed!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests