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 · 104 comments · Fixed by #8825
Open

refetchQueries not working when using string array after mutation #5419

reinvanimschoot opened this issue Oct 8, 2019 · 104 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.

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

ijreilly added a commit to twentyhq/twenty that referenced this issue May 31, 2024
…f using refetchQuery (#5684)

Closes #5057.

RefetchQuery is unreliable - [it won't be executed if the component is
unmounted](apollographql/apollo-client#5419),
which is the case here because of the redirection that occurs after the
mutation.
We want to avoid using refetchQuery as much as possible, and write
directly in the cache instead.
Weiko pushed a commit to twentyhq/twenty that referenced this issue May 31, 2024
…f using refetchQuery (#5684)

Closes #5057.

RefetchQuery is unreliable - [it won't be executed if the component is
unmounted](apollographql/apollo-client#5419),
which is the case here because of the redirection that occurs after the
mutation.
We want to avoid using refetchQuery as much as possible, and write
directly in the cache instead.
@emil14
Copy link

emil14 commented Jun 26, 2024

Any updates? That's a critical bug

@jerelmiller
Copy link
Member

Hey @emil14 👋

I'm not actually sure if there is a bug here. From what we've established so far, this seems to be an issue more about ergonomics of the API than an actual underlying bug. Please take a look at the comments from me and @phryneas above.

Are you encountering something different something different than whats already been mentioned?

@vijayth2-cerebras
Copy link

This worked for me after creating a new one:

refetchQueries: [{ query: ListMyOrganizationsDocument }]

@luap2703
Copy link

To my and I think many others understanding, the whole idea of refetchQueries is that I can invalidate/refetch any query from any mutation (although they don't know their respective existing) just with a smooth query name Document reference.

Now being forced to invalidate/refetch queries either by 1. specifically pointing towards them with their exact configuration, or by 2. manually jumping into the cache to modify stuff seems to be a huge design flaw - I mean, how would the query know which pagination/filtering variables are currently used on eventually +10 other queries being inactive but turning active once a create/delete operation has been performed?).

Now being forced to (due to 1. my mutation now knowing the inactive queries and to 2. minimize boilerplate cache-modification code) set all list queries in the application to "network-only" to have a smooth UI when delete or create operations occur does kinda remove the whole idea of a cache.

Is there no way to allow a flag on the refetchQueries array objects to say, "refetch this query if it is active immediately, if it is not active, invalidate the query in cache (i.e., somehow mark/invalidate its prop in the ROOT_QUERY) and refetch next time needed?
I could think of an additional options prop for mutations like invalidateCache: [DocumentNode] or an additional flag on the refetchQueries arr-objects, like refetchQueries: { query: DocumentNode, invalidateInCache: true}

It feels like there have been plenty of different threads now reporting this unexpected and broadly unwanted behavior.

Here: #7878

Or here: #11357

Or here: #5422

Since again a lot of time has passed since this was discussed last time, would you mind to look at this once again and do you know if any upcoming changes address this phryneas jerelmiller?

@UPSxACE
Copy link

UPSxACE commented Jul 14, 2024

Since this is exactly how it's supposed to work, refetchQueries is useless in my opinion

@phryneas
Copy link
Member

phryneas commented Jul 15, 2024

@luap2703 I agree that this function is generally confusing, but it's also very widely used - a change to it would be breaking (even so in a subtle way that we'd want to avoid even in a major).
And adding more functionality to it will only make it more confusing.

We hear your frustration, and there's a good chance that we'll come up with a replacement with a different name in the future, but it's probably not going to be very soon, I'm sorry.

@luap2703
Copy link

Thank you @phryneas for replying!

Since there's no major change incoming, what would you recommend as "best practice" to overcome the current limitation? Manually adjusting the cash?

@phryneas
Copy link
Member

Tons of nuance there, unfortunately - in the end, what works best for you.
I would probably combine it with a manual cache invalidation if I read your case here right, but for anyone else reading this: please don't take this as blanket advice, but apply your situation accordingly!

@StasNemy
Copy link

I fixed it removing refetchQueries.

Problem is that this query wasn't called yet I guess

@ryancrunchi
Copy link

Same issue for me.
I have a component querying a list of items with query name ListGet.
On another page (where list of items is not mounted), I can create a new item (mutations and so on...).
After the new item is created I call refetchQueries({ include: ['ListGet'] }) but it throws warning message Unknown query named "ListGet" requested in refetchQueries options.include array, despite the list component was mounted previously.

👉 This only happens in prod build. In dev build, the ListGet is refetched.

Tested with apollo-client versions 3.9.11 to 3.11.8

arnavsaxena17 pushed a commit to arnavsaxena17/twenty that referenced this issue Oct 6, 2024
…f using refetchQuery (twentyhq#5684)

Closes twentyhq#5057.

RefetchQuery is unreliable - [it won't be executed if the component is
unmounted](apollographql/apollo-client#5419),
which is the case here because of the redirection that occurs after the
mutation.
We want to avoid using refetchQuery as much as possible, and write
directly in the cache instead.
@kkx64
Copy link

kkx64 commented Oct 21, 2024

Possible solution with expected outcome

If anyone stumbles upon this, I had a specific use case where we would write queries like so:

const getXQuery = (param1: string) => gql`
query get_x_query {
   get_x_query(args: {param: "${param1}"}) {
      field1
   }
}`

I wanted to invalidate the cache only if param1 was a certain value (e.g. an ID of an object). After some digging this is what ended up working perfectly:

client.cache.modify({
      fields: {
        'get_x_query': (existing, { storeFieldName }) => {
            if (storeFieldName.includes('the-id-I-want-to-invalidate-for')) {
              return undefined;
            }
          return existing;
        },
      },
    });
client.cache.gc();

Then I wrote this helper function:

/**
 * Invalidate queries in the cache based on the query name and the string that the query contains.
 * @param queries - An array of objects containing the query name and the string to invalidate the query if it contains.
 * @param queries[].query - Which query to invalidate.
 * @param queries[].invalidateIfContains - Invalidate the query if it contains this string. Otherwise, invalidate the entire query.
 */
export const invalidateQueries = (
  queries: { query: string; invalidateIfContains: string }[],
) => {
  queries.forEach(({ query, invalidateIfContains }) => {
    graphqlClient.cache.modify({
      fields: {
        [query]: (existing, { storeFieldName }) => {
          if (invalidateIfContains) {
            if (storeFieldName.includes(invalidateIfContains)) {
              return undefined;
            }
            return existing;
          }
          return undefined;
        },
      },
    });
    graphqlClient.cache.gc();
  });
};

Which you can then use like so:

          invalidateQueries([
            {
              query: 'get_x_query',
              invalidateIfContains: `the-id-I-want-to-invalidate-for`,
            },
          ]);

Hope this helps someone out, I can see it also working even if you're not using functions for defining the query strings like in my project.

@benderillo
Copy link

Possible solution with expected outcome

If anyone stumbles upon this, I had a specific use case where we would write queries like so:

const getXQuery = (param1: string) => gql`
query get_x_query {
   get_x_query(args: {param: "${param1}"}) {
      field1
   }
}`

I wanted to invalidate the cache only if param1 was a certain value (e.g. an ID of an object). After some digging this is what ended up working perfectly:

client.cache.modify({
      fields: {
        'get_x_query': (existing, { storeFieldName }) => {
            if (storeFieldName.includes('the-id-I-want-to-invalidate-for')) {
              return undefined;
            }
          return existing;
        },
      },
    });
client.cache.gc();

Then I wrote this helper function:

/**
 * Invalidate queries in the cache based on the query name and the string that the query contains.
 * @param queries - An array of objects containing the query name and the string to invalidate the query if it contains.
 * @param queries[].query - Which query to invalidate.
 * @param queries[].invalidateIfContains - Invalidate the query if it contains this string. Otherwise, invalidate the entire query.
 */
export const invalidateQueries = (
  queries: { query: string; invalidateIfContains: string }[],
) => {
  queries.forEach(({ query, invalidateIfContains }) => {
    graphqlClient.cache.modify({
      fields: {
        [query]: (existing, { storeFieldName }) => {
          if (invalidateIfContains) {
            if (storeFieldName.includes(invalidateIfContains)) {
              return undefined;
            }
            return existing;
          }
          return undefined;
        },
      },
    });
    graphqlClient.cache.gc();
  });
};

Which you can then use like so:

          invalidateQueries([
            {
              query: 'get_x_query',
              invalidateIfContains: `the-id-I-want-to-invalidate-for`,
            },
          ]);

Hope this helps someone out, I can see it also working even if you're not using functions for defining the query strings like in my project.

Thanks! This, perhaps, is the most suitable solution for many who posted here.
Should be mentioned though that one shall pass correct name as "query" for this to work. E.g.:

const SELECT_STUFF = gql`
query GetMeStuff($name: String!) {
   stuffz(filters: {name: $name}) {
      id
      essence
   }
}
`;

So pass the stuffz as query name as that is what is in the cache.

@charpeni
Copy link
Contributor

charpeni commented Dec 19, 2024

Hey 👋

I know this is a long-running issue and that multiple issues have been brought here over the years, but I'm convinced that some of the issues described here could be related to an issue (#12164) that just got fixed (#12236) and released as 3.12.4.

A quick summary of the issue is that when passing DocumentNode to refetchQueries, it used to be compared by reference, where identical DocumentNode could trigger an Unknown query requested in options.refetchQueries warning because their reference differs, even though they are technically identical and definitely an active query. I couldn't pinpoint the exact reason why it was happening (multiple references), but I believe one contributing factor, especially in production, could be bundle splitting, where a query document node could be duplicated or even instantiated multiple times.

I invite you to read the issue and the pull request if you would like to learn more about this issue. Otherwise, make sure to update to 3.12.4 and test it out! In our case, it fixed all weird behavior, where refetchQueries was only working half the time.

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