apollo-client: refetchQueries not working when using string array after mutation
Intended outcome: After a create mutation, I want to refetch the list query to have the newly added item be displayed in the list, making use of the variables which the list query is run with previously.
As per the documentation, this should be doable:
Please note that if you call refetchQueries with an array of strings, then Apollo Client will look for any previously called queries that have the same names as the provided strings. It will then refetch those queries with their current variables.
Actual outcome: When using the operation name as string in the refetchQueries array, no query is refetched, nothing is updated and no network activity is visible.
Versions
System:
OS: macOS Mojave 10.14.6
Binaries:
Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
Yarn: 1.19.0 - /usr/local/bin/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v10.16.3/bin/npm
Browsers:
Chrome: 77.0.3865.90
Safari: 13.0.1
npmPackages:
@apollo/react-hooks: ^3.1.0 => 3.1.1
apollo-cache-inmemory: ^1.6.3 => 1.6.3
apollo-cache-persist: ^0.1.1 => 0.1.1
apollo-client: ^2.6.4 => 2.6.4
apollo-link: ^1.2.13 => 1.2.13
apollo-link-context: ^1.0.19 => 1.0.19
apollo-link-error: ^1.1.12 => 1.1.12
apollo-link-http: ^1.5.16 => 1.5.16
apollo-link-logger: ^1.2.3 => 1.2.3
react-apollo: ^3.1.2 => 3.1.2
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 65
- Comments: 91 (19 by maintainers)
Commits related to this issue
- Fix temporarily not working refetchQueries in production > https://github.com/apollographql/apollo-client/issues/5419#issuecomment-973154976 — committed to gwak2837/alpaca-salon by gwak2837 3 years ago
- Allow refetchQueries to refetch observerless queries (again). This PR is a follow-up to PR #8825, which was first released in Apollo Client v3.4.14. As a number of commenters have reported in issue #... — committed to apollographql/apollo-client by benjamn 2 years ago
In case anyone is still interested, this behavior happens because when you delete something, the component holding the original query didn’t get unmounted (normally because you just show a popup), while when creating a new item you redirect to another screen (i.e. create item page) unmounting the page holding the original query.
Apollo maintains a list of observable queries at any given time, which is based on which components are currently mounted that defined a query (i.e. through
<Query>...</Query>
oruseQuery
). When a component gets unmounted - at least in the React world - , Apollo removes it from the list of observable queries by callingQueryManager.tearDownQuery
when the component unmounts. DuringrefetchQueries
, when given a Query name string, apollo searches only the observable queries and because it can’t find it, it doesn’t execute a refeetch. If you provide a{ query: ..., variables: .. }
as a refetch value, Apollo bypasses this search and always executes the query you’ve given it, as if it was a completely new query (since it has the doc and the variables it needs to execute it)I don’t know if @hwillson would like to give us more context as to whether there is a way around this (except from not using the React or by making sure that we unmount as little as possible on crucial refetches).
In short:
refetchQueries
will only work with strings if the component that defined the original query is not unmounted. On the contrary, it will always work when using the{ query... , variables: ... }
style.The whole purpose of using a string is that you should NOT have to provide the exact object as Apollo should automatically refetch the query with the current query variables.
I have a very similar problem to the one you are experiencing here, I’m on version 3.3.11. I am basically using refetchQueries with a string array from a component A and I expect the component B to have the query refreshed when it’s mounted again. But I’ve noticed something that hasn’t been mentioned here. If I run the code in development mode the code behaves as expected, if I do run in production mode (after the
yarn build
) I experience the same problem of refetchQueries not working. Not sure any of you experiences this as well.Still an issue in 3.1.5 on May 22nd 2020. This is an important feature, frequently used, that should be fixed
Ok, sorry for triple-posting here. I had a lot of time to kill on a train.
tl;dr:
refetchQueries
has a history of not working with queries owned by unmounted components. There was a fix, but it was deleted.Here’s what I’ve figured out from picking over a bunch of Github issues (some of this may be wrong and I’m new to Apollo’s innards, so take it with a grain of salt):
refetchQueries
andupdateQueries
both used to work like you’d think (give them a query that was run before and it’ll run it again). It worked because Apollo Client (thenapollo-react
) inadvertently kept a reference to every query.refetchQueries
andupdateQueries
. (bug here, and this PR has a better description of the problem).updateQueries
without triggering re-renders. Since queries were kept around forever, you could reference them any time in the future inrefetchQueries
andupdateQueries
. Everyone rejoiced (PR here).refetchQueries
to continue accepting an array of strings “temporarily”. I’m not clear if or how the problem that the Recycler solved was fixed in another way.react-apollo
was merged into the@apollo/client
repo.refetchQueries
still accepts querying by name, but I’m not sure if that’s on purpose or if it’s just because this is an old problem in a big repo. I’m not sure if there’s a catalog of all previously-called queries somewhere accessible from the client.The solution I’m going to try is to use the
update
option to evict the part of the cache that’s stale and force any related queries to rerun. Maybe something like this:Thanks everyone for the comments and feedback we appreciate it! The Apollo Client team is taking a look at this problem again and will keep you all informed of when a fix will be released.
I think this needs to have option to refetch non observable queries. and also not latest varibales - but all variables for this query
those flags can solve a lot of issues
Just to repeat my comment from above more concisely, the goal here as a developer is to be able to say “please no longer use the cached result of this query which may or may not be mounted/active but which I now know is invalid”. Whatever mechanism you can come up with for simply achieving that would be hugely appreciated.
(I know it can be done by manual cache manipulation but that’s way more painful than is desirable in most situations.)
Why was this issue closed? It looks like there’s a lot of errors even in recent releases? I’m also running into similar issues
I have simple workaround solution for this - just add that query in the same function with mutation:
@benjamn maybe reopen issue, what you think?
We have the identical issue to @szamanr.
@Off2Race - unfortunately it’s not as simple as “When they do mount, the queries should update normally.” Of course that does happen, but in this case “updating normally” is pulling the data from the cache, which is now out of date because of the mutation where we asked for refetch.
Partly this is a fundamental issue with the Apollo design, where instead of manual cache invalidation the choice was query refetching, which is a slightly different thing and harder to achieve (and provably more expensive). If you look at react-query (https://react-query.tanstack.com/guides/query-invalidation) it does it the other way and turns out to be much simpler and easier to use in practice.
@benjamn I had hoped that https://github.com/apollographql/apollo-client/pull/8825 would fix this but unfortunately I’m still seeing it in 3.4.16 post-merge of that PR.
Apollo needs to either:
The last one here would treat it as an invalidation request, which presumably 99% of the time is the desired behaviour. Most apps don’t really care if the refetch happens immediately or next time the data is required (if at all).
This is still an issue on 3.4.7
Reproduction: https://codesandbox.io/s/floral-sea-cg45v?file=/src/App.js
Navigate to ‘Mutation’ and click ‘Mutate’.
You’ll see a console warning:
However as per the docs https://www.apollographql.com/docs/react/data/mutations/#refetching-queries
it should work, as the query
Slim
was previously executed. The issue seems to be that the component with the query is no longer in the tree, but the docs don’t mention that as a requirement. I’m not sure if the docs are wrong, or it’s a bug.I came accros this issue. I create a new item from a modal, while the listing page is in the background. After the mutation, I redirect to the detail page of the new item. Also, when the mutation is completed, I can see the listing query is refetched (in browser dev-tools). But the listing query result in the cache not getting updated.
If I cancel the redirect, which comes right after the mutation, there is no problem. After the query is refetched, cache gets updated. If I keep the redirect, but make
awaitRefetchQueries: true
in the mutation options, there is no problem again.I think, when the page that observes the query gets unmounted, even if the network request gets completed, cache can not be updated.
A cool way could be invalidating a query even if it wasn’t observed by any active component. Using this way, one could just tell the cache that the query result is now invalidated. And the first call to that query would be done by a network request. So, user creates a new record. Get redirected to the detail page of the new item. User may go to other pages, it doesn’t matter. But when we come to the listing page again, we would trigger a network request, instead of showing the stale data in the cache.
This is just a simple idea of course. There may be positive/negative points about this.
Edit: After some thinking, I feel like using
refetchQueries
to refresh active queries andfetchPolicy: network-only
for things like redirecting to listing pages might be much much more easy to implement and more maintainable. Using cache results is a very strong technique for things like infinite loaders, multiple isolated components which use the response from same query etc.Most of the times I just don’t feel it’s ok to change the default
fetchPolicy
, but it is a good option to use for many use cases.@espinhogr got the same issue. In my case refetch was performed on a component that was already unmounted, and that - for some reason worked well on dev, but didn’t on prod. Check your warnings (red ones lol), maybe that’s the case for you as well.
What happened with this issue, please!!
#6017
It is still an issue in
^3.7.11
versionIt works for me if I have a query and a mutation in the same component/route, but it does not work if I call it in the component/route that does not have the query to be refetched.
Hi, @benjamn – I just upgraded my project to 3.4.16 (which should have #8825) but I’m still getting the following warning when I use
refetchQueries
with mutations.In this case,
QueryIWantToRefresh
is used in various other components that weren’t mounted at the time the mutation was executed. The warning appears benign (more a nuisance than anything). And fortunately, the workaround I posted earlier still seems to work. But I thought you’d want to know.Is there other information I can share to help you figure out the cause? Should I log a new GitHub issue since this one is closed?
Thanks!
Hi, @kalote – I ran into this issue as well. The only workaround I could come up with was to use the new client.refetchQueries method, specify all “active” queries, and filter out the ones I didn’t watch to refetch using the onQueryUpdated function. See the example below:
on the other side,
refetchQueries
with the{ query..., variables: ... }
style also refetches queries that never were queried before. Which is also a behavior I would not prefer in our use cases.Just for the sake of sharing what I found, evicting data from the cache works like a charm.
It’s nice because I don’t have to think about all possible queries that could be impacted by my mutation — I just specify the part of the cache to evict and the queries refetch themselves the next time they’re active.
Here’s roughly what I have:
If I wanted this code to be more efficient and a little more clever, it could manually update my cache locally with my new data rather than re-querying for everything. But as a first step, this seems fine for most of our use cases.
@daveslutzkin Thanks for being patient on this issue, we know it’s a big problem for many. 😅 We were considering this issue for an upcoming release, but had to move some things around due our current focus on
useBackgroundQuery
anduseFragment
which are 2 new hooks schedule to be released in 3.7I definitely want to bring this back to the team to discuss and try to determine when we may be able to resolve it. I’ll keep everyone posted on this issue.
I am having the same issue: If I run the code in development mode the code behaves as expected, if I do run in production mode (after the yarn build) I experience the same problem of refetchQueries not working.
The first component has this query:
then, I run this in another component.
It will trigger the refetch of the query
perksTribeQueryConnection_PerksTribeQuery
as expected inDev
, but NOT inProd
.For now, as a workaround, we use
await client.resetStore()
. At least we know that everything will be updated but not happy at all with this solution.We are using
@salemhilal no worries! It’s actually can be easier to parse comments in smaller chunks that one larger one. Thanks for sharing your experience here.
Hi all! My team couldn’t wait release with this fix, because I had to hack apollo client 😃 Instead client.refetchQueries I used refetchQueries into mutation and set array [query: … , variables: …]. But how I get all DocumentNode of active queries? I have implemented this hook:
By applying this hook, we get all queries maybe which are repeated in array. This is not good for optimisation. The solution would be to use uniqBy from lodash.
The decision for client.refetchQueries is controversial (we breaking typescript patterns of private fields) but it work for me.
Hi, @szamanr – To be fair, I’m not sure it matters whether the queries are actually refetched because the components themselves are unmounted. When they do mount, the queries should update normally. In our case, it’s the warning itself which causes concern. It’s either just a nuisance (causing alarm where none should exist) or it points to a separate problem that we can’t see.
Apollo client 3.2.5 and issue is not fixed. Any updates?
I had a similar problem but was resolved by providing the exact object used to fetch the query which may include any variables, query options and etc.
@daveslutzkin thanks for this clarification. I think you and I are probably using the same terms to mean different things, so I’m probably confusing you even more 😬. Apologies for that!
Something to clarify is that Apollo’s
InMemoryCache
is normalized, so the concept of “invalidating a query” isn’t quite right. When I think of “invalidating a query”, I typically couple that concept with whole query caching.Rather than thinking of this as “invalidating a query”, think of it more as “invalidating a field or entity” in the cache, where an “entity” is defined as a chunk of data typically keyed by its
__typename
andid
(one might also call this a “model”.Book
andUser
might be an “entity” for example). Apollo’s cache is built so that if it can’t fulfill all of the data needs for a query from the cache, it will go and fetch that data from the server. This concept is what I think you’re alluding to when you say this:You probably already know this information, but I wanted to state this to ensure we are talking about the same thing. If you’re curious about a more in-depth look at whole query caching vs normalized caching and their behaviors, let me know and I’d be happy to add an example to explain this.
This is super helpful to know! In this case, the
cache.evict()
suggestion I gave you is likely not going to work well since you’re looking to invalidate a field on a record, not evict an entire record entirely from the cache.I totally hear you here and and agree the current implementation is a bit of a limiting detail and a leaky abstraction. We’ll see if we can come up with some strategies for making this operate a bit more reasonably while allowing for the optimizations to take place, but this is likely not something we will look at until after we release v3.8 (sometime in the next few weeks).
Again, I’d still like to at least determine if this is a bug even with “active” queries so that we can classify this issue accordingly. A fix for active queries not refetching would likely be a quicker fix than determining a new strategy to make this work for all previously called queries. If you have some more details to share here to help classify this behavior, that would be helpful.
If
refetchQueries
with a string array is not working well for you, here are a couple ways I can think of that should work now in the latest version of Apollo (v3.7.14
at the time of this writing).Solutions
refetchQueries
does also take an item in the array that is an object with aquery
andvariables
property. This method of refetching queries will guarantee a refetch on the query, even if the query is inactive.Take this approach with a grain of salt and a bit of caution if you decide to try this. Our codebase literally calls this approach “legacy” and we don’t document it. I don’t have the history or context to fully understand why this is the case, so it may come with its own quirks.
useMutation
orclient.mutate
(not sure which API you’re using) has anupdate
option that allows you to make direct cache updates. Here you can invalidate the field that contains the list. The next time you go to execute the query with this field, Apollo will see that it can only fulfill partial data for the query and will go fetch data from the server for you.You can use
cache.modify
to handle this behavior. We include anINVALIDATE
sentinel object in modifier functions that allows you to invalidate data for a field. This approach might look something like this:Check out the list of examples on how
cache.modify
might help you here.Does this help a bit more?
@daveslutzkin appreciate the response! Here’s my 2 cents
I can absolutely agree here. I tend to like APIs that fit a reasonable mental model without needing to know nitty gritty or surprising details. This definitely feels like it fits into that bucket since you need to know the concept of an “active” query to use this feature with a string array.
That being said, there are some performance-related concerns that we are also trying to balance here and this is something I think we do a poor job of communicating if I’m being honest. When queries stop being used, we tend to throw them away and allow the garbage collector to do its job so that we can avoid memory leaks with lots of objects building up over time. Its difficult to know when a query might or might not be used again. Since queries can “disappear” when they aren’t being used, it makes sense that a query that has been thrown away can no longer be found by its name, hence why you’re seeing the warning about a missing query.
This doesn’t mean this problem can’t be solved, that the API today is perfect, or that we can’t iterate on this to make it better. I’m simply trying to convey some tradeoffs we have to make that sometimes result in less-than-ideal outcomes, even if some of the benefits aren’t easily seen.
This is spot on. I absolutely hear you as I’ve experienced this plenty myself. As an app grows in complexity, its impossible for any one developer to keep everything in their head, nor is it reasonable to expect this to be the case. The tools you use should help you, not hinder you.
This is a general theme that I’d frankly like to see us tackle in v4: simplification. Apollo Client has been around for quite a while by this point, and like any project that exists for any amount of time, parts of it can grow crusty or feel complex for no reason (after all, we’ve been trying to make backwards compatible changes for years at this point!). These types of ergonomics are absolutely something I’d love to see us approach with v4 to make Apollo feel simple.
About the best I can promise you right now is that we are absolutely looking at this and will be investing a considerable amount of time for v4 to try and remove a lot of these types of friction points. We will be sharing more in the coming months as the picture for v4 becomes clearer.
@barbalex you’re absolutely welcome to try out different clients and pick what suits you the most. The worst thing we can do is hold you hostage just because you’re familiar with this library. Each library has its own set of tradeoffs with varying ideas on how to approach things. There are absolutely things Apollo can do that TanStack Query can’t and vice-versa. Up to you to decide which set of tradeoffs work the best for you.
If you’d like to help us shape the future for v4, we’d absolutely love feedback on the friction points you’ve experienced and what specifically makes Apollo Client feel complex to you. Apollo Client shouldn’t feel complex for simple cases and I’m bummed that this is the sentiment. It just means we have an opportunity to make some improvements to remove these barriers.
That being said, since this issue isn’t about feedback and about
refetchQueries
, feel free to send me an email with feedback or hit me up on Twitter at @jerelmiller.Ok back to the regular scheduled programming on the topic of
refetchQueries
😆. Outside of the ergonomics of the current solution, I’d still like to determine if this is actually a bug in the current implementation, or if its a matter of trying to fetch an “inactive” query. I’d appreciate some help here trying to make this determination.@jerelmiller
I said this earlier in the thread but I’ll try to outline it again. I think the problem is one of ergonomics not functionality - as you say, “functioning as we the maintainers expect”.
When using the library, generally people just want to say “I now know that this data isn’t valid so refetch it if I ask for it again”.
But what apollo-client expects of people is to say “I now know that this specific active query isn’t valid so please refetch it now”, which is a much less predictable thing and very hard for a developer to work with in the general case.
For instance, it’s very possible that a mutation will invalidate data in a way that I can predict, but that the queries that reference that data aren’t particularly predictable. Or if they are predictable now, they won’t be as the structure of the app inevitably changes. I don’t care if the queries are active or not, and in general I can’t predict whether they will be, because app code is complicated and the same mutation can be accessed from multiple places.
Also ideally I’d only refetch this data if it’s actually asked for again, rather than refetching it eagerly which can only lead to overfetching.
In that case what apollo-client expects of me is to write manual cache manipulation, which is complex and error-prone code, and again can change unpredictably as the structure of the app and therefore the cache changes.
This is completely workable and yet pretty frustrating and expensive to actually work with in practice. Ideally what apollo-client would have instead is some nice shorthand for “invalidate this piece of data when this mutation returns” and then I could just pass that and move on, rather than spending half an hour reverse-engineering the structure of the cache and writing specific code to invalidate the pieces that matter.
Does that make any sense?
This issue has been open for 4 years. It is one of the reasons we are migrating to tanstack query.
I have an update! I kept an eye on the Apollo dev tools, and here’s what I noticed:
I think the problem I’m seeing is that
refetchQueries
doesn’t have a full picture of all the queries that have previously ran. For this reason, providing just the name of a previous query isn’t enough, and providing a query from the query manager may not be enough either.I could manually write some cache eviction logic to handle this particular case, but that sounds cumbersome given that I know the query I want to refetch.
I’ll note that I only see queries getting evicted from the query manager in production, and not in dev or local builds of my application. I’m not sure why that’s happening. I can’t tell if the query evictions are supposed to happen, or if it’s an indication of a bug.
I am also experiencing this on an app of mine. Users can reach a given action page by different routes, and after performing an action - mutation, I need to refresh the queries for all those routes, hence I end up overloading the
refetchQueries
option with queries not run yet, hence getting a console warning.It would be great if the option could refetch only those queries that had run before, and ignore the rest.
It would be great if we could have support for refetching unmounted/unobservable queries.
having the same issue on
3.4.16
. the query from an unmounted component is not refetched, regardless of whether i specify it usingrefetchQueries
when defining a mutation or callapolloClient.refetchQueries
.@alessbell you were not able to reproduce the issue because your app is rendered under React
StrictMode
, which renders components twice when running the app in dev mode. This has a side effect with apollo : each query is run twice, consequently each query is duplicated in the observed queries array.When unmounting a component, only one of each duplicated queries is removed from said array, refetch is thus successful because it matches the query name with the leaked query in the array. I’ve removed
StrictMode
from your exemple and am able to reproduce the issue : https://codesandbox.io/s/epic-darwin-2jf6td?file=/src/index.jsThis difference of behaviour between dev and prod build is also very confusing (took me an afternoon of debugging to understand what was going on) so we could at least work on removing the leak under strict mode ? Or maybe what’s in the works for 3.8 will fix that ?
Hi, any updates?
I am having the same issue. After a book gets inserted in my database, I need to get all books again to show them in the list. But refetchQueries is not working.
Each time after adding a book, I have to manually refresh my page. Version: “@apollo/client”: “^3.4.16”, “graphql”: “^15.7.2”,
@daveslutzkin i guess a workaround is to set the cache policy to “cache-and-network” on the request you’re trying to refetch.
@dylanwulf there are definitely tradeoffs to each approach. If you know your query is “active” (like it sounds you have), then
refetchQueries
will probably work better for you. In the case of this issue where a query is inactive or has been cleaned up, the cache deletion might work better to force the query to re-execute when it becomes active again. Definitely a tradeoff to consider!@jerelmiller Ah that makes more sense, thanks! I’ve tried that in the past and it does work for refetching, but my main problem with it is that it causes my query data to disappear from the screen until the refetch completes. Not the best user experience, so I’m still sticking to
refetchQueries
for now.@dylanwulf oh you might be right. All this talk of invalidating data got my brain scrambled 😂.
DELETE
is probably the better option here as it deletes a field from an object entirely. My example above should read:It makes more clear what I have felt for quite a while: apollo is way too complex for simple cases. I have never fully understood it and it seems I have a lot to learn.
Which is why I have moved on to tanstack query. Where cache invalidation is so simple, even I have grasped it.
I just came across this myself in
^3.7.11
. So yes, it is still an issue.I modified https://github.com/apollographql/apollo-client/issues/5419#issuecomment-1226049136 to get a intersected list of active query names.
Usage:
I’ve tried this approach several times and the problem I keep running into is that evicting the cached data makes it disappear from my screen. I want the old data to continue being displayed while refetching, but I don’t see an easy way to do that with
cache.evict()
Based on this discussion, I have a theory I’d like to test (see #8825), which is that
QueryIWantToRefresh
is not refetched because it happens to have no observers, even though the developer explicitly requested the refetch.So it’s not possible to perform a refetch with the original query and variables? Doesn’t sound to me a good choice 😕
I have a similar issue. The weird thing is that when I call a mutation where it deletes a row, and then I call the refetchQueries with a string, it actually works. But after I insert a new row, nothing happens when I do the refetch. It makes no sense because the delete is still a mutation, so I’m not sure what the difference could be. Both mutations return the same values. I’m looking at the network requests, and I see that after the insert there is no request, but after the delete, there is a new request that actually do re fetch
The only way I could make this to work is to change the fetch policy to
cache-and-network
delete mutation
refetch
insert mutation
delete mutation hook
insert mutation hook
Yeah I totally agree with you there man it sure is a bug or documentation is wrong. Didn’t mean to sound like it ain’t a bug. It’s very inconvenient having to provide the exact same object especially multiple mutations in multiple places refetches the query.