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

refetchQueries not working when using string array after mutation #5419

Open
reinvanimschoot opened this issue Oct 8, 2019 · 91 comments · Fixed by #8825
Open

refetchQueries not working when using string array after mutation #5419

reinvanimschoot opened this issue Oct 8, 2019 · 91 comments · Fixed by #8825

Comments

@reinvanimschoot
Copy link

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

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.

@reinvanimschoot
Copy link
Author

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.

@jinshin1013
Copy link

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.

@tafelito
Copy link

tafelito commented Oct 9, 2019

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
image

refetch
image

insert mutation
image

delete mutation hook
image

insert mutation hook
image

@reinvanimschoot
Copy link
Author

I have witnessed exactly the same behaviour! It works fine when I refetch after I delete an item but not when I create one.

@mnesarco
Copy link

mnesarco commented Jan 7, 2020

Hi Friends, I have the same problem: refetchQueries works with update mutations, but nothing happens on insert mutations. Is there any known workaround?

@kirkchris
Copy link

Impacting us here also... 😫

@seanconrad1
Copy link

Also running into this issue

@onderonur
Copy link

onderonur commented Feb 25, 2020

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 and fetchPolicy: 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.

@cristiandley
Copy link

#6017

@3nvi
Copy link

3nvi commented Mar 12, 2020

I have witnessed exactly the same behaviour! It works fine when I refetch after I delete an item but not when I create one.

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> or useQuery). When a component gets unmounted - at least in the React world - , Apollo removes it from the list of observable queries by calling QueryManager.tearDownQuery when the component unmounts. During refetchQueries , 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.

@Emiliano-Bucci
Copy link

So it's not possible to perform a refetch with the original query and variables? Doesn't sound to me a good choice :/

@3nvi
Copy link

3nvi commented Mar 30, 2020

So it's not possible to perform a refetch with the original query and variables? Doesn't sound to me a good choice :/

It's only possible if you don't navigate away (i.e. unmount the component that performed the original query).

Your other option (which is not actually a solution) is to use a different networkPolicy so that when you revisit the component that held the query, a server-query is performed.

@Emiliano-Bucci
Copy link

I see; thanks!

@Momepukku
Copy link

@3nvi

refetchQueries will only work with strings if the component that defined the original query is not unmounted.

I have three components render on the same page which use the same query(and same operation name) but difference variables.
when I specify operationName as refetchQueries, only the last one got refetch.
Why?

@3nvi
Copy link

3nvi commented Apr 17, 2020

cause it literally does just that. It refetches the query with the latest variables. From apollo's side it's only 1 query with 3 different invocations, so i tries to refetch the "latest" invocation.

@slask
Copy link

slask commented May 22, 2020

Still an issue in 3.1.5 on May 22nd 2020.
This is an important feature, frequently used, that should be fixed

@moneebalalfi
Copy link

What happened with this issue, please!!

@Idan-Hen
Copy link

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

@jdmoliner
Copy link

Apollo client 3.2.5 and issue is not fixed. Any updates?

@andoks
Copy link

andoks commented Nov 27, 2020

May be related to #3540

@espinhogr
Copy link

espinhogr commented Feb 18, 2021

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.

@thomasjsk
Copy link

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

@mutefiRe
Copy link

mutefiRe commented Apr 30, 2021

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.

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.

@manuFL
Copy link

manuFL commented Jul 21, 2021

Same here, working on dev but not on prod.... please help

@maxsalven
Copy link

maxsalven commented Aug 10, 2021

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:

Unknown query named "Slim" requested in refetchQueries options.include array

However as per the docs
https://www.apollographql.com/docs/react/data/mutations/#refetching-queries

The name of a query you've previously executed, as a string

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.

@sakhmedbayev
Copy link

sakhmedbayev commented Apr 7, 2023

It is still an issue in ^3.7.11 version

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

Unknown query named "MediaList" requested in refetchQueries options.include array

@github-actions
Copy link
Contributor

github-actions bot commented May 8, 2023

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 8, 2023
@alessbell alessbell reopened this May 8, 2023
@apollographql apollographql unlocked this conversation May 8, 2023
@amaster507
Copy link

amaster507 commented May 10, 2023

Given that I cannot reproduce the bug in the latest version, I'm going to close this issue out

I just came across this myself in ^3.7.11. So yes, it is still an issue.


I modified #5419 (comment) to get a intersected list of active query names.

import { DocumentNode, useApolloClient } from "@apollo/client"
import { QueryInfo } from "@apollo/client/core/QueryInfo";

function intersect(a: string[], b: string[]) {
  const setB = new Set(b);
  return [...new Set(a)].filter(x => setB.has(x))
}

export function useGetActiveQueries(toRefetch: string[]) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const client = useApolloClient() as any; // any to get access to private props, 😬
  const initialQueries = client.queryManager.queries as Map<string, QueryInfo>;
  const queryDocuments: Record<string, { 
    query: DocumentNode | null 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    variables: Record<string, any> | undefined
  }> = {}
  const allQueries = initialQueries.values();
  for (const queryDoc of allQueries) {
    queryDocuments[queryDoc.queryId] = {
      query: queryDoc.document,
      variables: queryDoc.variables
    }
  }
  return intersect(Object.values(queryDocuments).map(doc => {
    const def = doc.query?.definitions[0]
    if (def?.kind === 'OperationDefinition') {
      return def.name?.value ?? ''
    }
    return ''
  }).filter(name => name !== ''), toRefetch)
}

Usage:

// inside Component
const refetchQueries = useGetActiveQueries(["QueryFoo", "QueryBar"])

const [exec, { loading }] = useMutation(mutation, {
  refetchQueries,
})

@dizzyjaguar
Copy link

dizzyjaguar commented May 16, 2023

Running into this right now with a delete modal and a deleting an item from a datagrid. Was hoping to trigger a re-query for the datagrid but it's not recognizing my string query.
My issue was running the query with SSG

@jerelmiller
Copy link
Member

@amaster507 interesting solution! Feel free to keep using it, but I'd recommend a couple tweaks that is more safe (i.e. not subject to breaking changes) and doesn't require you to reach into the internals as much.

  1. Rather than getting the queries like this:
const initialQueries = client.queryManager.queries as Map<string, QueryInfo>;

Use client.getObservableQueries('active') instead. If you want all queries, not just the active ones, you can use client.getObservableQueries('all'). This will return a map of query IDs to ObservableQuery. Here you can get the query from the query property:

const activeQueries = client.getObservableQueries('active');
const queryDocuments = Array.from(activeQueries.values())
  .map(observableQuery => observableQuery.query);

client.getObservableQueries is a public method and won't be subject to breaking changes, so it would be safer to use this.

  1. It looks like you don't use variables when getting the intersection of of query names, so you should be able to omit that from your queryDocuments type.

  2. When mapping over your queryDocuments, your implementation assumes the query definition comes first with fragments defined afterward. Fragments can also be declared before the query operation and be considered a valid GraphQL document. If you'd like this to be a bit more robust, consider using the getMainDefinition function exported from @apollo/client/utilities (you can see an example here). This will return an OperationDefinitionNode if your document contains one, or the first FragmentDefinitionNode if your document only contains fragments. From here you can extract the name.

import { getMainDefinition } from '@apollo/client/utilities';
return intersect(Object.values(queryDocuments).map(doc => {
  const def = getMainDefinition(doc.query);

  if (def?.kind === 'OperationDefinition') {
    return def.name?.value ?? ''
  }
  return ''
}).filter(name => name !== ''), toRefetch)

@dizzyjaguar I'm curious, when you're seeing this issue, is the query you're trying to refetch an active query? It's possible its not refetched for that reason (in which case the helper by @amaster507 would help). If you'd like to refetch regardless of whether the query is active, consider passing the query document to your refetchQueries instead of the string:

// where MY_QUERY is a query wrapped in `gql`
import { MY_QUERY } from './some/path';

mutate({
  // ...
  refetchQueries: [MY_QUERY]
})

If the query is active and you're still not able to refetch using a string array, we could use some help providing a reproduction where we can look at this a bit more in-depth. As @alessbell tried above this seems to be functioning as we the maintainers expect, but there could be something else that we just aren't seeing. A reproduction goes a long way to help us better diagnose the issue.

Hope this helps!

@daveslutzkin
Copy link

daveslutzkin commented May 16, 2023

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

@barbalex
Copy link

barbalex commented May 16, 2023

Does that make any sense?

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.

@jerelmiller
Copy link
Member

@daveslutzkin appreciate the response! Here's my 2 cents

I think the problem is one of ergonomics not functionality

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.

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.

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.

@daveslutzkin
Copy link

@jerelmiller

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.

I think that's perfectly understandable. But my point here is that I don't actually want to request refetch of a query by name, I just do that because it's the way the library seems to suggest (by providing the refetchQueries argument in the first place and documenting it). I actually want to invalidate/evict a piece of data, and this would also completely avoid the very valid performance concerns you raise.

@jerelmiller
Copy link
Member

jerelmiller commented May 16, 2023

@daveslutzkin ah gotcha, apologies that I misunderstood what you were getting at.

Perhaps this is also something not well communicated in our docs? You can do this very thing with the cache.evict() function. So if you wanted to remove the Book record with id of 5, you could do something like this:

const book = { __typename: 'Book', id: 5, ...otherFields }

cache.evict({ id: cache.identify(book) })

And if you want to remove dangling references to the evicted record, you can call cache.gc()

Is this what you're looking for?

@daveslutzkin
Copy link

@jerelmiller Hm, maybe? Though that should presumably be taken care of transparently by returning an object with id in the mutation response, so maybe I'm confusing myself and describing it wrong - maybe it is a query (usually a list query) not a piece of data.

Just looking through our code, most of the refetchQueries arguments are either list or aggregate queries that we know are invalidated in a particular place.

So maybe my point is more that we don't care and don't want to care whether those queries are "active". That feels like the library's concern not ours, purely an optimisation detail.

OK, so maybe what we want is to be able to say "invalidate this query so next time its result is requested, it doesn't come from the cache because we now know that cached value will be invalid".

So then:

  • If the query is currently being used by a mounted component, it refetches instantly.
  • If the query isn't currently being used by a mounted component, but then is requested by a component which is mounted later, it doesn't come from the cache but is then refetched.
  • If the query is never requested again, it's never refetched.

Which I think is what happens when you call cache.evict({ fieldName: "queryName" })? Correct me if I'm wrong!

But because:

a) there's no evictQueries option to mutations; and
b) evict isn't mentioned in the docs here https://www.apollographql.com/docs/react/data/mutations/#updating-the-cache-directly

then it doesn't feel like it's a recommended way to proceed.

@jerelmiller
Copy link
Member

jerelmiller commented May 17, 2023

@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 and id (one might also call this a "model". Book and User 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:

invalidate this query so next time its result is requested, it doesn't come from the cache because we now know that cached value will be invalid

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.

most of the refetchQueries arguments are either list or aggregate queries

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.

my point is more that we don't care and don't want to care whether those queries are "active". That feels like the library's concern not ours, purely an optimisation detail.

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.

OK, so maybe what we want is to be able to say "invalidate this query so next time its result is requested, it doesn't come from the cache because we now know that cached value will be invalid".

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

  1. refetchQueries does also take an item in the array that is an object with a query and variables property. This method of refetching queries will guarantee a refetch on the query, even if the query is inactive.
refechQueries: [{ query: QUERY, variables: {...} }]

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.

  1. Consider modifying the cache directly to invalidate the field that contains the list of data you'd like to refetch. This approach jives with the way you are thinking about this:

invalidate this query so next time its result is requested, it doesn't come from the cache because we now know that cached value will be invalid

useMutation or client.mutate (not sure which API you're using) has an update 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 an INVALIDATE sentinel object in modifier functions that allows you to invalidate data for a field. This approach might look something like this:

mutate({
  update(cache) {
    // NOTE: You may have to provide an `id` option here if the field you're looking to
    // invalidate is not a top-level query field. 
    cache.modify({
      fields: {
        listField(_, { INVALIDATE }) {
          return INVALIDATE;
        }
      }
    })
  }
});

Check out the list of examples on how cache.modify might help you here.

Does this help a bit more?

@dylanwulf
Copy link
Contributor

We include an INVALIDATE sentinel object in modifier functions that allows you to invalidate data for a field.

It is my understanding that INVALIDATE only causes queries to re-read that field from the cache, not refetch, am I misunderstanding?

@jerelmiller
Copy link
Member

jerelmiller commented May 17, 2023

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

mutate({
  update(cache) {
    // NOTE: You may have to provide an `id` option here if the field you're looking to
    // delete is not a top-level query field. 
    cache.modify({
      fields: {
        listField(_, { DELETE }) {
          return DELETE;
        }
      }
    })
  }
});

@dylanwulf
Copy link
Contributor

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

@jerelmiller
Copy link
Member

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

@sakhmedbayev
Copy link

sakhmedbayev commented May 22, 2023

What I ended up doing is just placing all refetch functions for all previous queries in the react context as a dictionary and using it from there in my app.

const {..., refetch} = useQuery(...)

setAllPrevQueries(
  {
     ...allPrevQueries,
     keyOfSomePrevQuery: refetch
  }
)

// and then
const {allPrevQueries} = useAllPrevQueries()

const handleRefetchSomewhere = () => {
    allPrevQueries[keyOfSomePrevQuery]()
}

@abarreir
Copy link

abarreir commented Jun 8, 2023

Hi all 👋

I was trying to understand whether this is still an issue, so I forked a sandbox mentioned above and updated Apollo Client to the latest version, 3.7.11. By visiting the Query page first, then navigating to the Mutation page and clicking "mutate" you can see both the mutation and the refetch (triggered via refetchQueries: ["Slim"] in App.js) fire: https://codesandbox.io/s/unruffled-napier-wzdv9m?file=/src/App.js:715-739

If anyone has any other questions here, do let me know! Given that I cannot reproduce the bug in the latest version, I'm going to close this issue out, but it will remain unlocked for any follow-up comments/questions for 30 days. Thanks!

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

This 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 ?

@DiRover
Copy link

DiRover commented Oct 12, 2023

Hi for everyone, today I got stuck with the same issue. I have component A that has a list and a form for creating items for this list. There is also component B that only has a list from component A. In dev mode, everything works correctly, but in prod mode, the list in component B does not update automatically when creating a new item, only after page reload.

const [createFun] = useMutation(GQL, {
        refetchQueries: [LIST_GQL], // DocumentNode object parsed with gql
    });

@andreimatei
Copy link

Hi @jerelmiller! I've hit this Unknown query [object Object] requested in refetchQueries options.include array error even though I'm trying to refetch queries by "document", not by string. Is this surprising?

You've said:

If you'd like to refetch regardless of whether the query is active, consider passing the query document to your refetchQueries instead of the string:

And yet, I seem to see the same behavior when I'm using the document (or at least I think I am).

This is my code:

export const GET_COLLECTIONS = gql(`
  query GetCollections($input: GetCollectionsInput!){
    getCollections(input: $input) {
      id
      name
    }
  }
`);

const [captureSnapshotsMutation] = useMutation(CAPTURE_SNAPSHOTS, {
  refetchQueries: [GET_COLLECTIONS],
  // The GET_COLLECTIONS query is not always active when this mutation runs,
  // so we need to also manually evict its results from the cache. We really
  // want this query to be re-executed the next time the snapshots page is
  // rendered.
  update(cache) {
    cache.evict({id: "ROOT_QUERY", fieldName: "getCollections"});
  },
});

This results in Unknown query [object Object] requested in refetchQueries options.include array printed when the GET_COLLECTIONS query is not active. Notice the [object Object] in there - that's presumably saying something interesting.
One thing to note is that I'm using "@graphql-codegen/cli to generate types from my gql. The type of GET_COLLECTION thus is TypedDocumentNode<GetCollectionsQuery, Exact<{input: GetCollectionsInput}>>.

Is all this surprising / indicative of a bug?

@phryneas
Copy link
Member

phryneas commented Jan 24, 2024

Hi @andreimatei,
thanks for reporting that [object Object] there - that was an error in the warning message and will be fixed over in #11516.

That said, it's unrelated to the problem you are having here - there are in fact three different things you can pass into the refetchQueries array:

  1. query names
  2. query documents (like you did here)
  3. the legacy { query, variables } object that @jerelmiller hinted at here.

Nr. 1. and 2. should behave pretty much the same - I believe you wanted to use 3. instead here, so your code would need to change like this:

const [captureSnapshotsMutation] = useMutation(CAPTURE_SNAPSHOTS, {
-  refetchQueries: [GET_COLLECTIONS],
+  refetchQueries: [{ query: GET_COLLECTIONS }],

Keep in mind that this "legacy" style will always call a query with the variables you put in here, independently if the query is currently in cache or not. So it's less of a "refetch this" and more of a "fetch this".

@andreimatei
Copy link

Got it, thank you for the explanation!

@Emiliano-Bucci
Copy link

@jpvajda No news right? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment