-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
DocumentNode
passed to refetchQueries
are compared by references
#12164
Comments
Hey @charpeni 👋 Thanks for that thorough write-up and deep dive into the code! It is certainly interesting that you're seeing intermittent behavior here. The only thing I could possibly think of on the client end that might play a factor is document transforms. We have a default transform that we use to add apollo-client/src/core/QueryManager.ts Lines 152 to 156 in 77b8931
In those cases where you're getting a mismatch (i.e. the "Unknown query" warning), I'd be curious if the the stored document is the transformed one or your original document. You can use the graphql.js import { print } from 'graphql';
print(document) // => "query MyQuery { ... }" You said though that using Would you be willing to check the above case to see if the default document transform plays a role here or not? On your solutions:
While I like this idea, this solution unfortunately won't handle anonymous queries like the raw That said, I like your idea about comparing strings rather than relying on referential equality with raw Is this something you'd want to try out? |
Oh, thanks for mentioning Now that you mentioned the document transform could cause it, I made sure we don't have other document transforms that could be conflicting. The only other document transform we have is configured within the GraphQL Code Generator, where we automatically query for the But I found something interesting that I just remembered I should mention here. It seems like the Ah, the internal |
It's a bit difficult to see, but that
The only reason we disable the document transform cache in the So as long as you're using Hope the experimentation with |
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better. |
Issue Description
We recently noticed that
refetchQueries
was acting strangely, it was inconsistent and did not work in all locations.We use a combination of
@apollo/client
and GraphQL Code Generator withclient-preset
and we pass theDocumentNode
directly to the mutation'srefetchQueries
function. We noticed that in some locations, it was working fine, but in other locations, using the exact sameDocumentNode
wasn't working as expected and leading to the following warnings:Unknown query X requested in refetchQueries options.include array
. Using Apollo Dev Tools confirmed the expected query was still active.Investigation Notes
I started investigating to understand why it was working half the time and found some really interesting things. Most of the relevant logic happens in
QueryManager
class, more specifically, thegetObservableQueries
method.The first thing happening is that we declare a new
Map
(queryNamesAndDocs
) which its keys will be containingstring (query name) | DocumentNode
:apollo-client/src/core/QueryManager.ts
Line 856 in 77b8931
Then, we loop over
include
, which is basically what we passed torefetchQueries
, to build the previousMap
(queryNamesAndDocs
):apollo-client/src/core/QueryManager.ts
Lines 859 to 869 in 77b8931
My understanding is that we build a
Map
of queries, and we start with the one we passed torefetchQueries
, but we default the boolean (the value, which is whether they are included or not) to false as they weren't checked yet. As of the key, we rely onDocumentTransform
to transform it before using the wholeDocumentNode
as a key.Then, we loop over
this.queries
and look for matches betweenqueryName
anddocument
inqueryNamesAndDocs
:apollo-client/src/core/QueryManager.ts
Lines 871 to 900 in 77b8931
I'm particularly interested in the following logic:
apollo-client/src/core/QueryManager.ts
Lines 890 to 899 in 77b8931
If we were passing string values to
refetchQueries
, then we would be relying onqueryName
, but as we're passingDocumentNode
, it means we rely ondocument
instead. We previously saw that we created an entry in theMap
with theDocumentNode
transformed as the key. So, that's where I added breakpoints to understand what's going on.I double-checked to make sure that the query I passed to
refetchQueries
was still active, and it was the case, then I checked that specific condition:apollo-client/src/core/QueryManager.ts
Line 893 in 77b8931
It turns out that the inconsistency comes from that exact condition. When everything works, then
queryNamesAndDocs.has(document)
returnstrue
, but in some cases, it returnsfalse
. I looked at both objects, and they were identical, but it wasn't enough for the condition. We need to have the exact sameDocumentNode
reference, otherwise, the object key won't be found inqueryNamesAndDocs
:We can easily reproduce this by making sure we're passing a new instance of a
DocumentNode
:Will be producing:
Unknown query X requested in refetchQueries options.include array
.See the reproduction in 🔗 CodeSandbox.
I haven't been able to figure out why they won't have the same reference. I made sure we were only using one single instance of Apollo Client. I tagged both
DocumentNode
with a special key, I took a heap snapshot and was able to confirm that in multiple locations, they were the same object reference but something in between is changing that.I found this issue: #5419, where multiple people described a similar issue to what we experienced—not directly related to the top-level message, but down there, where everything is getting mixed, people mentioned similar issues and especially that it works fine locally, but not in production. I can think of multiple contributing factors to this issue, but they all come to the fact that we use the
DocumentNode
as a key and then compare it by reference.I wonder if one major contributing factor could be how bundle splitting is configured and how we can end up with the same
DocumentNode
contained in multiple bundles and therefore, it's getting complicated to guarantee that for a given query, we'll always reuse the same instance of aDocumentNode
. The mentioned issue above also suggested to either passing queries as a string or by using the legacy object:{ query, variables }
, but I would like to avoid the last option at all cost because it forces us to pass the exact same variables that were used when we should just be refetching the latest variables used.Workaround
There's a workaround available to make it work all the time, it's not convenient and I would like this to make it bake in in Apollo Client, but in the meantime, using the
DocumentNode
and extracting the query name can be achieved viagetOperationName(DocumentNode)
, so you don't have to use magic strings in your code to refer to queries:Potential Solutions
This led me to brainstorm on the following solutions...
Note
I would be happy to send a pull request or at least, start one, but I wanted to confirm which direction to take first.
Retrieve the operation name from the
DocumentNode
and use it as a key (same asqueryName
)Do we even need to store the whole
DocumentNode
object as a key? We could reduce complexity by removing it and reusing the workaround suggested above, but internally, withinrefetchQueries
's logic.E.g., we could still accept both
string | DocumentNode
, but refactor the following logic to extract the query name from theDocumentNode
viagetOperationName
:Compare by value, not by reference
Warning
I don't believe this is particularly a good idea, but I had to share about it because the whole issue is the comparison point and we could be stuck here in case we need to keep
DocumentNode
as keys.Another option could be to store the object as a string, where we could use
JSON.stringify
before storing the value:So we can either compare it as a string or convert it back to an object via
JSON.parse
, but I would be concerned about performance issues when callingJSON.stringify
andJSON.parse
back and forth, and also because queries length are unpredictable, they could be quite small or quite big.Link to Reproduction
https://codesandbox.io/p/devbox/apollo-client-refetchqueries-issue-f626v9?workspaceId=a02ec0ab-7d00-4b4d-b865-a63aba3fe944
Reproduction Steps
No response
@apollo/client
version3.11.4
The text was updated successfully, but these errors were encountered: