I 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
^1.1.4
^0.1.0
^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.
Encountered a sub-selection on the query, but the store doesn't have an object reference.
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
The following query
query {
loadItem {
title
description
child {
name
}
}
}
is requesting a "name" sub selection on the field "child" but the store doesn't have a child object reference.
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
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:
query workspace($workspaceId: String!) {
workspace(id: $workspaceId) {
id
contract {
id
versions {
id
type
status
}
}
}
}
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:
{
"data": {
"workspace": {
"id": "00000000-0000-AAAA-0000-00000000000A",
"contract": {
"id": "00000000-0000-BBBB-0000-00000000000B",
"versions": [],
"__typename": "ContractType"
},
"__typename": "WorkspaceType"
}
}
}
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:
query chat($workspaceId: String!, $eventId: ID!) {
workspace(id: $workspaceId) {
id
chatroom {
id
event(id: $eventId) {
id
resource {
... on ContractType {
id
versions {
id
type
}
}
}
}
}
}
}
}
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
, and status
, but for the chat GQL query, it requests only a version's id
and type
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 its getCurrentQueryResult
method. Within the getCurrentQueryResult
method, the manager reads from the data store's cache (this.dataStore.getCache().read
). The cache's read
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's getCurrentQueryResult
silently swallows the error with a try-catch block and simply executes a maybeDeepFreeze
:
public getCurrentQueryResult<T>(observableQuery: ObservableQuery<T>) {
const { variables, query } = observableQuery.options;
const lastResult = observableQuery.getLastResult();
const { newData } = this.getQuery(observableQuery.queryId);
if (newData) {
return maybeDeepFreeze({ data: newData.result, partial: false });
} else {
try {
// the query is brand new, so we read from the store to see if anything is there
const data = this.dataStore.getCache().read({
query,
variables,
previousResult: lastResult ? lastResult.data : undefined,
optimistic: true,
});
return maybeDeepFreeze({ data, partial: false });
} catch (e) {
return maybeDeepFreeze({ data: {}, partial: true });
}
}
}
(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
and status
, but since feature B used a query that only gets a contract's versions with fields id
and type
but no status
, this causes the data store's cache read
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
Ha ! It seems like I'm not the only one facing this missing data problem ! It's been 3 days since I tried to debug the following problem (only on iOS). And it is still not debugged...
I figured out that the problem comes from a specific screen but I don't exactly know what makes this specific data return null
(in my case : userLogged
from my queryUserLoggedInfos
query) and how to resolve the issue.
In your case, It seems that comes from a missing field but how did you find it ?
Considering the following snippet :
render() {
if (this.props.queryUserLoggedInfos.networkStatus === 1) return null;
const { userLogged } = this.props.queryUserLoggedInfos;
console.log("=========render userLogged==========");
console.log(userLogged);
console.log("====================================");
(...)
I got the following result on iOS :
[apollo-cache-persist] Restored cache of size 90659
=========render userLogged==========
{id: "5a2c769d4ef79d57e29c940a", pseudo: "sof", email: "[email protected]", avatar: "ws5icnrjntmd4bbbvlay", tribu: {…}, …}
====================================
=========render userLogged==========
{id: "5a2c769d4ef79d57e29c940a", pseudo: "sof", email: "[email protected]", avatar: "ws5icnrjntmd4bbbvlay", tribu: {…}, …}
====================================
remote-redux-devtools: Socket connection errors are being suppressed.
This can be disabled by setting suppressConnectErrors to 'false'.
SocketProtocolError {name: "SocketProtocolError", message: "Socket hung up", code: 1006, stack: "SocketProtocolError: Socket hung up↵ at SCSocke…a/node_modules/expo/tools/hashAssetFiles:2316:42)"}
=========render userLogged==========
null
====================================
[apollo-cache-persist] Persisted cache of size 90595
So after the first two renders, the cache is filled but at the end of the third it becomes empty. So user is logged during few milliseconds and is logout just after... And this problem does not appear on Android...
On Android there only are two renders instead of three.
[apollo-cache-persist] Restored cache of size 35420
=========render userLogged==========
Object {
"__typename": "User",
"avatar": "muv3zrworf6bg2mcap9o",
"email": "[email protected]",
(...)
}
====================================
=========render userLogged==========
Object {
"__typename": "User",
"avatar": "muv3zrworf6bg2mcap9o",
"email": "[email protected]",
(...)
},
}
====================================
[apollo-cache-persist] Persisted cache of size 33281
Really strange behavior...
Versions :
"apollo-cache-persist": "^0.1.1",
"apollo-client-preset": "^1.0.5",
"apollo-link": "^1.0.7",
"apollo-link-ws": "^1.0.4",
"apollo-utilities": "^1.0.4",
I have probably hit this same problem.
I have two queries called from two different screens. Both queries retrieve some combination of objects from server but one of them retrieves less attributes then the other one.
What I noticed is that the queries need to be called in specific order for this bug to appear.
Lets say I have two screens with two queries.
Screen A requests model 1 with attributes X, Y and Z.
Screen B requests same model 1 only with attributes X and Y.
Now following happens:
Open screen A - query A executes and everything loads as it should, data is OK, contain X, Y, Z
Open screen B - query B executes and everything loads as it should, data is OK and only contain X, Y
Open screen A - query A is not executed again (data from cache), data is not OK, only contain X and Y
Another scenario:
Open screen B - query B executes and everything loads as it should, data is OK, contain X and Y
Open screen A - query A executes, everything is OK, contain X, Y, Z
Open screen B again - query B executes, everything is OK, contains X and Y
Open screen A again - no query, data only contain X and Y
From this I presume query A has to be loaded before query B is loaded for the A query to break.
This is not the first time I run into this problem. Previously we solved it by using fragments to always load all the data, because it was not that big of a overhead. This time it is a bit larger problem, because there is lot more data.
I'm also having trouble with this behavior
@MrLoh Thank you for reporting the issue and digging into! I'm working on a reproduction: https://codesandbox.io/s/lyj02q5347. It seems like the query works for first order values under the root query, so we'll have to create a little more complicated repro. If you have a chance to fork the codesandbox and associated https://glitch.com/edit/#!/worried-song-1 to create a repro, that would be incredible!
I'm going to be pulled away from the problem for a bit, so may not have a chance to finish up the repro fully in the next couple of weeks
I also ran into this issue (random, 20% of the time it still worked) when using the same child to 2 different object types.
#comments is a child here:
order {
total
comments {
text
}
}
#comments is also a child here:
user {
id
comments {
text
}
}
This was fixed when I changed the __typename for user comment to be different from the __typename for order comment.
Seems like adding the prop partialRefetch
is a reasonable workaround and otherwise we will have to wait patiently to v3 😿
https://www.apollographql.com/docs/react/essentials/queries.html
If anyone here is able to continue the work in https://github.com/apollographql/apollo-client/issues/2920#issuecomment-419536054 and provide a runnable reproduction that shows this happening, that would be awesome!
meet the question too
query ProductGraph {
nodes {
products{
id
name
relationships {
id
start_node
end_node
type
}
related_nodes {
product_push_times {
name
uid
usage
create_time
progress_str
id
}
}
}
}
}
I can only get id and name in relationships and related_nodes
but id or name of products can not get
meet the question too
query ProductGraph {
nodes { products{ id name relationships { id start_node end_node type } related_nodes { product_push_times { name uid usage create_time progress_str id } } } }
}
I can only get id and name in relationships and related_nodes
but id or name of products can not get
my current solution is use Aliases of graphql
like this
nodes{
products{
_id: id
uid
_name: name
relationships{
id
}
related_nodes{
product_push_times{
id
name
}
}
}
}
that I can get the result _id and _name with the right value from graphql server
and the return json property name would be _id and _name
This has been addressed by https://github.com/apollographql/apollo-client/pull/4743, and will be coming in Apollo Client 2.6. You can set returnPartialData
to true
to have previous partial cache results used, instead of having the returned data set to an empty object.
Most helpful comment
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