React-apollo: Query returns `undefined` when same cache object is in multiple query responses

Created on 19 Dec 2018  Â·  13Comments  Â·  Source: apollographql/react-apollo

I have two queries in components that are on screen at the same time – one (me) returns the current user, and another (group) returns all members of a group. Once cached, the cached results of each query look as follows:

  me: User
    User:bryan-irace
  group({"id": 1}): Group
    Group:some-group-name
      members: GroupMembers
        results: [User]
          0: User:bryan-irace
          1: User:kevin-grant

I am seeing some very strange behavior when the User model returned by the me query is also included in the members.results array returned by the group query. Specifically, the me query’s callback is invoked multiple times:

  1. data.me returned undefined (network request has not occurred and cache has not been populated yet)
  2. data.me returns the correct model (model is now stored in the cache)
  3. data.me returns undefined again, despite the model being present on disk

This third invocation is problematic – I don’t know why it is being triggered to begin with, but if it’s going to be triggered, data.me should certainly be populated.

I can fix this problem by either:

  1. Removing the groups query altogether
  2. Modifying the data such that the user returned by the me query is no longer a group member

Obviously neither of these are suitable workarounds, but in both cases, the third me query callback invocation does not occur.

Please advise 🙏

Most helpful comment

This is a pretty easy mistake to make though, and the fact that this just returns undefined when the query’s data is visible in the cache via the Apollo inspector makes it really hard to figure out what's going on. No errors or anything.

All 13 comments

If it helps, here are the two different variations on the same model, the first from the me query and the second from the group query:

"admin": true,
"firstName": "Bryan",
"lastName": "Irace",
"bio": "Push code too late on a Friday – what is wrong with me",
"professions": [{
  "id": 2,
  "name": "Software Developer",
  "__typename": "Profession"
}, {
  "id": 8,
  "name": "Polyglot Engineer42",
  "__typename": "Profession"
}],
"phoneNumber": "+15559274956",
"city": "Chicago, NY",
"email": "[email protected]",
"website": "https://irace.me",
"linkedinUrl": null,
"facebookUrl": null,
"instagramUrl": null,
"twitterUrl": null,
"googleUrl": null,
"yelpUrl": null,
"foursquareUrl": null,
"slug": "bryan-irace",
"tags": [],
"avatar": {
  "large": "https://some/avatar/url",
  "thumbnail": "https://some/avatar/url",
  "__typename": "UserAvatar"
}
"firstName": "Bryan",
"lastName": "Irace",
"professions": [{
  "name": "Software Developer",
  "__typename": "Profession"
}, {
  "name": "Polyglot Engineer42",
  "__typename": "Profession"
}],
"slug": "bryan-irace",
"avatar": {
  "large": "https://some/avatar/url",
  "thumbnail": "https://some/avatar/url",
  "__typename": "UserAvatar"
},
"tags": [],
"__typename": "User"
}

Is there an id field that you left off? Not having an id field will cause a lot of issues, since it's used by apollo when normalizing to the cache.

@sbrichardson Thanks for getting back to me. In this case, the slug field is the unique identifier used for caching purposes.

OK, I (mostly?) figured this out.

First, if caching was turned off, this bug did not manifest itself.

Ultimately, the issue is that I was making one query (modified for brevity) like:

user {
  slug
  profession {
    name
  }
}

And another query like:

user {
  slug
  profession {
    id
    name
  }
}

Even though the users were the same, and using slug as their cache ID, the fact that the nested professions were not being correctly identified as the same reference (due to one having an id property and the other not), the cache was in a weird state and incapable of realizing that the two users were – in fact – the same reference.

This is a pretty easy mistake to make though, and the fact that this just returns undefined when the query’s data is visible in the cache via the Apollo inspector makes it really hard to figure out what's going on. No errors or anything.

Yes I've ran into that issue, was going to mention, typically it manifests if you remove an id field from a selection set accidentally, such as in a table UI, editing table columns to build a gql query dynamically.

Unfortnuately, this issue comes up and is resolved but the github issues are spread around in react-apollo, apollo-cache etc, so it compounds the troubleshooting effort.

https://www.apollographql.com/docs/react/advanced/caching.html#normalization

@sbrichardson That documentation that you linked to doesn’t really give any indication that a missing cache key on a child object would prevent the parent object from being properly normalized. It seems like this should theoretically be something that Apollo can handle, or at the very least provide a more helpful error message?

Been stuck on this for hours. What's the fix?
It happens almost randomly it seems.

@developdeez I've fixed this by making sure id field is always selected for all nodes (might be nice to have this done automatically if omitting a field breaks the app). I am also now re-using the same query for components that request the same object. This kind of defeats the _"only request what you really need"_ advantage of GraphQL, but is not a big deal for me.

See the react-apollo 2.5.6 release notes for details around the returnPartialData prop. It will help here. Thanks!

@hwillson I don't think that returnPartialData resolves the issue.

returnPartialData provides data from the cache immediately.
Here the issue is different: after the query data is returned from the backend, instead of it being available in data property, the data property is undefined.

As @Elijen said (emphasis mine):

by making sure id field is always selected for all nodes (might be nice to have this done automatically if omitting a field breaks the app)

Seeing the same issue as @vfonic. Was on react-apollo 2.1.2 .Moved to 2.5.7 as well. Issue still present.

@deepikagunda try adding id field to your query. That fixed it for me.

query SomeQueryYouHave {
  posts {
    nodes {
      id
      # ... your other fields go here
    }
  }
}
Was this page helpful?
0 / 5 - 0 ratings