Updating with missing data makes query fail silently
See original GitHub issueI have a typical usecase where a mutation update inserts a new element in a list. Very similar to the standard example from the docs.
But the query is of course much more complicated. In my case I am creating a rating that has two 1-1 relation to a movie and a user object. The update function has to update both. The issue was when updating the user object by inserting the rating with the corresponding movie id on the users rating list. All that works great so far. But the issue was that the query where the users ratings are requested was reading more information from the movie object than what was already loaded for some movies. Now the query just returned null. I took me about a day to figure out that the one missing field was the issue.
To illustrate lets say this is the query on the user object that gets the ratings:
query userRatings {
currentUser {
ratings {
id
movie {
id
title
poster
}
}
}
}
And this is the mutation
mutation createRating {
newRating: createRating(value: 4) {
id
movie {
id
title
}
}
}
Now when another query had already loaded the movie poster into the cache all was well, but when the poster was missing the userRatings
query simply returned null
without any errors being shown. In reality it was quite hard to figure out what even had happened because everything seemed to work but on the user profile screen the data suddenly disappeared.
Intended outcome:
I would have expected that there would be an error message from the userRatings
query and that just the missing data would have been null but the rest there. Basically how a query to the server would behave. It seems that client side cache updates don’t prduce errors in other queries.
Actual outcome:
Nothing indicated that there was any error in apollo, the data simply disappeared. I am on react native, and the data disappeared on a different screen, making this very hard to track down as I first needed to find out that the mutation even caused this and because it just happened for movies where the poster wasn’t already fetched by another query in the application.
Version
- apollo-cache-inmemory:
^1.1.4
- apollo-cache-persist:
^0.1.0
- apollo-client:
^2.0.4
Issue Analytics
- State:
- Created 6 years ago
- Reactions:21
- Comments:12 (3 by maintainers)
Top GitHub Comments
We’re also running into this issue, and like @MrLoh and @maierson, it’s also been very difficult to debug.
In our particular scenario, we have one feature (feature A) making a GQL query, like this:
When a new workspace is created on our platform, a contract also gets created and is associated with the workspace; however, the contract starts off with no versions. When the above query is executed against our GQL server for a newly created workspace, we get a result that looks like this:
So far, so good.
Elsewhere in the app, we have another feature (feature B) that makes a different GQL query that may include contract information:
Above, the query is fetching a chat event that has an associated resource. A resource can take on many different types, one of which is a contract type (union shown above for brevity). You’ll notice that in the first GQL query, it requests a contract’s versions having fields
id
,type
, andstatus
, but for the chat GQL query, it requests only a version’sid
andtype
fields.Alright, so here’s how things play out – brace for impact…
Eventually a new contract version is created, this will cause the system to send out a chat event to all users that are part of a workspace which the contract belongs to. The frontend web app that makes use of the apollo client will receive a message (via a websocket) notifying the app of a new chat event. Feature B in the web app will pick up on this notification and in turn make a call via the apollo client to get more chat event information. Once the graphql server passes back a response, the apollo client triggers all of the GQL queries associated with the workspace and contract, which in this scenario is feature A and B. In the case of feature B everything works. Nothing blows up. However, feature A does blow up, and when we look for errors we see nothing.
Like @MrLoh, the issues came down to the data passed back from the server and how apollo client reconciles it. Initially a contract’s
versions
field is null. Within apollo client, it has a query manager (QueryManager
) that will get a query’s current result via itsgetCurrentQueryResult
method. Within thegetCurrentQueryResult
method, the manager reads from the data store’s cache (this.dataStore.getCache().read
). The cache’sread
method will properly reconcile objects when they are null or an empty list. The cache simply ignores that object’s fields that a query wants. Where things go wrong is when two or more queries that want the same object but ask for different fields on that object. If a query asks for a field on an object that is undefined, the cache’s read method throws an error. That’s fine. The problem is that the query manager’sgetCurrentQueryResult
silently swallows the error with a try-catch block and simply executes amaybeDeepFreeze
:(see https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/QueryManager.ts#L969)
When digging in and examining the error, the error does include detailed information why the cache’s
read
failed. That would be super handy to know.So, going back to our scenario, for feature A, its query wants a contract version’s
id
,type
andstatus
, but since feature B used a query that only gets a contract’s versions with fieldsid
andtype
but nostatus
, this causes the data store’s cacheread
method to throw an error, but one that we don’t see.This took us a while to track down the root cause and really understand what was going wrong.
Given what we now know, I’d like to add on to @MrLoh and @maierson thoughts for what the intended outcome should be.
At minimum, there should be a way to configure apollo client so that instead of silently swallowing thrown read query errors, the client will instead loudly raises the error for dev’s to easily track down. Better yet, it would be really nice if there were a way to handle these type of read errors gracefully. For instance, the apollo client could provide some kind of hook to optionally handle read errors. When handling the error, queries that failed could be run again to fetch data each query expects.
Anyway, apologies for such a long comment. I figured it would be useful for anyone else who is going down this long, winding road. And thanks to @MrLoh for initially raising this issue 😊
Other details
^2.2.2
^1.1.7
^1.0.7
^2.0.4
I am also running into this and it is very hard to debug. I’m on the web so I do get an error but it’s very cryptic and not really actionable without extensive digging.
I feel it would indeed be much more useful if the cache returned null values for fields that are not yet present instead of blowing up. It would at least be easier to track down. Or if not can we at least have a reference to the query and the object missing in the error message? Something like
This way at least there’s a way to know what data exactly is missing (has not previously been requested - ie the child field). In my case this happens when I deploy the app to the server where it loads legacy data. Everything works well on the client side. So even more complicated to track down.
Thanks