Apollo-client: refetchQueries only refetches a single query

Created on 4 Jun 2018  ·  18Comments  ·  Source: apollographql/apollo-client

Intended outcome:

After a mutation, the refetchQueries prop is used and all queries that have matching names are refetched.

Actual outcome:

After a mutation, the refetchQueries prop is used and the first query for each matching name is refetched.

How to reproduce the issue:

I don't have time to create a reproduction right now, I'm hoping this rings a bell, if not I can create a repro.

Basically, we have several queries with the same name that have slightly different fields, eg. query foo { foo { id bar } } and query foo { foo { id meep } }. Since v2.1 of react-apollo, only one of those queries gets updated.

Version

It used to work before.

(This is a copy of https://github.com/apollographql/react-apollo/issues/1897 - apologies if that's bad form, but that one didn't get any replies and I thought that maybe it's more related to apollo-client anyway.)

🐞 bug 🙏 help-wanted

Most helpful comment

The inconsistent behavior of refetching with apollo has resulted in a ton of extra 'network-only' fetch-policies for important screens. It is probably one of the biggest pain points with apollo client, which is otherwise pretty seamless IMO.

I agree with @wmertens and @KeithGillette, an invalidation approach like that could work.

All 18 comments

Thanks for reporting this @wmertens. This definitely sounds like a bug. If you or anyone else that 👍'd this are able to put together a reproduction, that would definitely help get this resolved more quickly. With regards to where this might be happening in the codebase, this is where all queries should be refetched:

https://github.com/apollographql/apollo-client/blob/eecaf222d2a882a8786d0c3711bb0fea079c873b/packages/apollo-client/src/core/QueryManager.ts#L1157-L1169

this.queryIdsByName is a map that associates query names with an array of query ID's, so it's either not being built properly, or the code in refetchQueryByName isn't iterating over all query ID's properly (or something else entirely, but this is where I'd start troubleshooting).

I'm in a similar boat. We have multiple instances of <Query react components, each loads the same query, but with different variables. When using graphql HOC with option refetchQueries, only the latest query is refetched.

Confirmed apollo has > 1 queries registered with different IDs, however, only the latest one gets refetched

screen shot 2018-08-06 at 3 36 43 pm

The refetched query also appears to ignore the skip: true option when refetched by name; although, that is probably another issue.

So I remembered I wrote a version of refetchQueries on a project last year. Plugged it in and it happens to work on my new usecase as well! All queries get refetched, regardless of name-collision.

@wmertens It might work for you too, feel free to try it:

import { type ApolloClient } from 'apollo-client'
import { compact, flatten, map, values } from 'lodash'

export const refetchQueriesByName = (client: ApolloClient, queryNames: Array<string>) =>
  Promise.all(map(queryNames, (queryName: string) => refetchQueryByName(client, queryName)))

// We're re-writing QueryManager refetchQueryByName to be less brittle:
// https://github.com/apollographql/apollo-client/blob/88a77511467b2735e841df86073ee3af51e88eec/src/core/QueryManager.ts#L1004
export const refetchQueryByName = (client: ApolloClient, queryName: string) => {
  const refetchedQueries = getObservableQueriesByName(client, queryName)

  return refetchObservableQueries(refetchedQueries)
}

export const refetchAllQueries = (client: ApolloClient) => {
  const refetchedQueries = getAllObservableQueries(client)

  return refetchObservableQueries(refetchedQueries)
}

export const refetchObservableQueries = (refetchedQueries: Array<Object>) => {
  const promises = compact(map(refetchedQueries, (observableQuery: Object) => {
    if (isObservableQueryRefetchable(observableQuery)) {
      return observableQuery.refetch()
    }
  }))

  return Promise.all(promises)
}

export const getObservableQueriesByName = (client: ApolloClient, queryName: string): Array<Object> => {
  const { queryManager: { queries, queryIdsByName } } = client
  const queryIds = queryIdsByName[queryName] || []
  return map(queryIds, (queryId: string) => queries.get(queryId).observableQuery)
}

export const getAllObservableQueries = (client: ApolloClient): Array<Object> => {
  const { queryManager: { queries, queryIdsByName } } = client
  const queryIds = flatten(values(queryIdsByName))
  return map(queryIds, (queryId: string) => queries.get(queryId).observableQuery)
}

export const isObservableQueryRefetchable = (observableQuery: Object): boolean => observableQuery.options.fetchPolicy !== 'cache-only'

EDIT: Oct 2019 – the apollo internals have changed since posting this. It no longer works.

Spent a while on this one.

@hwillson there is a repro here: https://github.com/arist0tl3/apollo-client-test

When refetchQueries is passed in as an array (refetchQueries: ['Dates'] in the repro), then it looks like refetchQueries resolves to an actual query object (the first one it finds?)

When refetchQueries is passed in as a function that resolves to an array (refetchQueries: () => ['Dates'] in the rero), then refetchQueries is an array of strings, and refetchQueryByName seems to work as expected.

I know I've seen other ['Query'] vs () => ['Query'] discussions around here, so I'm not sure what the expected behavior is, or if the docs need to be updated, etc.

so I'm not sure what the expected behavior is, or if the docs need to be updated, etc.

Yeah like so many other things on Apollo that you expect to work like they should, or at least how it is explained in the docs. Sometimes the project feels abandoned.

@TSMMark Thank you for the code. I think it should really be part of Apollo core; being able to call refetchQueries outside of a mutation is pretty essential.

I might incorporate it as part of a library, and I'll be sure to include a link to this comment.

@TSMMark I've created a package called apollo-refetch-queries with your code. I've added you as an author if you're comfortable with that.

@asselstine No problem! Hope it works for you, thanks for bundling it into a package. Have you had any issues with that code in the wild so far?

@TSMMark Just the ordering of the functions; I had to reorder them to get it to work, and also force the casting of the queryManager to any so that we can pull out the private variables. Also it didn't like the import { type ApolloClient } from 'apollo-client' so I changed it to import { ApolloClient } from 'apollo-client'.

FWIW: I thought we were experiencing this issue (which surfaced with either the refetchQueries: ['QueryName'] or refetchQueries: () => ['QueryName'] syntax) but I think it's actually the case that ApolloClient is only refetching queries with active subscriptions, not all of those with matching names that still exist in the cache. In previous testing, I had not paid attention to the subscription status of the queries but realize now that only 1 was actively subscribed when refetchQueries was invoked. In further testing, if I do not unsubscribe from a watchQuery when the calling component is destroyed so that the subscription remains active, then refetchQueries does indeed refetch all the queries of the same name but different variables.

Ok, great find! So the issue is really that non-active queries with the
same name need to be removed from cache?

On Thu., Mar. 21, 2019, 9:48 p.m. Keith Gillette notifications@github.com
wrote:

FWIW: I thought we were experiencing this issue (which surfaced with
either the refetchQueries: ['QueryName'] or refetchQueries: () =>
['QueryName'] syntax) but I think it's actually the case that
ApolloClient is only refetching queries with active subscriptions, not all
of those with matching names that still exist in the cache. In previous
testing, I had not paid attention to the subscription status of the queries
but realize now that only 1 was actively subscribed when refetchQueries
was invoked. In further testing, if I do not unsubscribe from a watchQuery
when the calling component is destroyed so that the subscription remains
active, then refetchQueries does indeed refetch all the queries of the
same name but different variables.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/apollographql/apollo-client/issues/3540#issuecomment-475396192,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWlpuSoRBBdvbcxZMRI1k_Zy8JHsxQks5vY_AEgaJpZM4UY-Y0
.

Well, I suppose if queries with no subscribers were removed from the cache, that would also force a fetch to occur, @wmertens, but then it wouldn't be much of a cache, as lots of queries will be fetched and re-read from the cache with a query instead of a watchQuery and never have any subscribers. I know cache invalidation is hard, but here must be another solution to this…

Err, no - only refetchqueries should invalidate the cache entries.

Yes, invalidating the cache entries of queries with matching names on a refetchQueries seems like an appropriate approach, @wmertens. Right now, it appears that when the last subscriber to an ObservableQuery unsubscribes, the ObservableQuery.tearDownQuery method removes the query from the QueryManager.queryIdsByName Map so that when QueryManager.refetchQueryByName is called, the given query is no longer in its Map and therefore not refetched, but also not referencable by name for invalidation…

The inconsistent behavior of refetching with apollo has resulted in a ton of extra 'network-only' fetch-policies for important screens. It is probably one of the biggest pain points with apollo client, which is otherwise pretty seamless IMO.

I agree with @wmertens and @KeithGillette, an invalidation approach like that could work.

Same problem for me.
I will try to do my custom refetch.
Thanks @TSMMark for the code ! (Edit: but we can't access queries anymore =( )

For posterity -- the other thing to check is the queryDeduplication option on the client.
If you set queryDeduplication to false and the bug goes away, then it's because you have inflight requests.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

elie222 picture elie222  ·  3Comments

treecy picture treecy  ·  3Comments

jamesreggio picture jamesreggio  ·  3Comments

gregorskii picture gregorskii  ·  3Comments

stubailo picture stubailo  ·  3Comments