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 only refetches a single query #3540

Closed
wmertens opened this issue Jun 4, 2018 · 19 comments
Closed

refetchQueries only refetches a single query #3540

wmertens opened this issue Jun 4, 2018 · 19 comments

Comments

@wmertens
Copy link
Contributor

wmertens commented Jun 4, 2018

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

  • apollo-client@2.2.8
  • react-apollo@2.1.2

It used to work before.

(This is a copy of apollographql/react-apollo#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.)

@hwillson
Copy link
Member

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:

private refetchQueryByName(queryName: string) {
const refetchedQueries = this.queryIdsByName[queryName];
// early return if the query named does not exist (not yet fetched)
// this used to warn but it may be inteneded behavoir to try and refetch
// un called queries because they could be on different routes
if (refetchedQueries === undefined) return;
return Promise.all(
refetchedQueries
.map(id => this.getQuery(id).observableQuery)
.filter(x => !!x)
.map((x: ObservableQuery<any>) => x.refetch()),
);
}

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

@TSMMark
Copy link

TSMMark commented Aug 6, 2018

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.

@TSMMark
Copy link

TSMMark commented Aug 6, 2018

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.

@TSMMark
Copy link

TSMMark commented Aug 7, 2018

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.

@arist0tl3
Copy link

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.

@spyshower
Copy link

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.

@asselstine
Copy link

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

@asselstine
Copy link

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

@TSMMark
Copy link

TSMMark commented Feb 14, 2019

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

@asselstine
Copy link

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

@KeithGillette
Copy link
Contributor

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.

@wmertens
Copy link
Contributor Author

wmertens commented Mar 22, 2019 via email

@KeithGillette
Copy link
Contributor

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…

@wmertens
Copy link
Contributor Author

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

@KeithGillette
Copy link
Contributor

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…

@TSMMark
Copy link

TSMMark commented Mar 22, 2019

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.

@Kisepro
Copy link

Kisepro commented Feb 16, 2020

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 =( )

@ibash
Copy link

ibash commented Oct 29, 2020

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.

@hwillson
Copy link
Member

A lot of the Apollo Client internals have changed since v3 was launched. We recommend trying a more modern version of @apollo/client. Let us know if you're still encountering this issue. Thanks!

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

No branches or pull requests

9 participants