question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested objects with no ids and different selection sets returns no data

See original GitHub issue

Intended outcome: With returnPartialData false, requesting non-matching selection sets from nested, denormalized objects (with no id), should alert me that there is data loss, and therefore complete data can never be achieved.

In this case, our User has a set of Preferences, and under preferences there are CalendarPreferences. Neither Preferences nor CalendarPreferences has their own id.

Take this very contrived query (I promise there’s a realistic query with the same symptoms)

query {
  userWithPreferenceOne: user(id: 1) {
    id
    preferences {
      calendarPreferences {
        preferenceOne
      }
   }
  userWithPreferenceTwo: user(id: 1) {
    id
    preferences {
      calendarPreferences {
        preferenceTwo
      }
   }
 }
}

Apollo Client should alert me that because there is no custom merge function defined for Preferences or CalendarPreferences, and because there are no ids, it won’t merge them, and therefore either userWithPreferenceOne or userWithPreferenceTwo will never have complete data.

At the very least, warnAboutDataLoss should fire, however in a case like this where data will never resolve, an error seems appropriate.

Actual outcome: When a query like the one above is made using the useQuery hook. The results are

error: undefined,
data: undefined,
loading: false

There are no errors or warnings logged, so there is no indication that anything failed. However the query is ‘finished’ and yet there is no data and no error.

With returnPartialData on, the incomplete data is returned (turning this on is undesirable, we need all the data). Turning on partialRefetch leads to an infinite loop of trying to load the complete data and failing.

I stepped through the warnAboutDataLoss function and the problematic object (preferences, in the above example) early returns on this condition: https://github.com/apollographql/apollo-client/blob/a975320528d314a1b7eba131b97d045d940596d7/src/cache/inmemory/writeToStore.ts#L362-L365 Since it is only checking top level properties, it verifies that ‘existing’ and ‘incoming’ both have ‘calendarPreferences’ fields, but it does not catch that one only has preferenceOne and one only has preferenceTwo.

How to reproduce the issue: I have created a reproduction here: https://github.com/ford-outreach/react-apollo-error-template Here is the diff (apologies that it’s a little noisy, prettier went to town): https://github.com/apollographql/react-apollo-error-template/compare/master...ford-outreach:master

Versions 3.0.2 in our app 3.2.5 in the reproduction

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:11 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
stephenhcommented, Jan 14, 2021

@benjamn FYI we had a variation of this (actually an infinite loop of queries, not data loss) that, oddly enough went away in 3.4.0-beta.4 (must be due do that cache canonicalization? I don’t see any other commits in there since 3.3.6…).

In our case, for 3.3.6, we had two queries, where one was getting only keyA from a valueObject:

anEntity(id:1) {
  id
  valueObject {
    keyA { id ... }
  }
}

And separate query on the same page was getting both keyA and keyB:

anEntity(id:1) {
  id
  valueObject {
    keyA { id ... }
    keyB { id ... }
  }
}

(Note that our valueObject’s keys actually point to id-ized entities, which I think is different from the OP’s schema.)

In our case, these two GraphQL queries were invalidating each other, and the QueryInfo.setDiff breakpoint’s diff/oldDiff was basically showing:

valueObject {
  keyA { ... }
  keyB: undefined
}
valueObject {
  keyA { ... }
  keyB: null

I.e. one query was causing keyB to be undefined (probably the one that didn’t call it) and the other query was causing keyB to be null (which it was validly null from our backend response).

This diff wasn’t causing our react components to re-render, however it was causing an ~infinite loop in the cache where each query would take turns invalidating / refetching the other. Here’s the Initiator stack trace from one of the ~10s/100s of graphql requests in the network tab:

image

So, odd things:

  1. Not sure why in the OP case, it caused data loss, but for us caused an infinite loop of queries, and

    (Actually maybe this is just b/c we have multiple queries on the same page touching the same anEntity(sameId) { valueObject } but with different projections.)

  2. Not sure why 3.4.0-beta.4 “fixed” it … i.e. we saw no looping of queries … is this good/bad/expected?

    I.e. is 3.4.0-beta.4 is merging value objects when it’s not supposed to? Or maybe we got lucky and the fact that our diff was “just” keyB: null vs. keyB: undefined, the canonicalization now threats those as “good enough” the same?

I dunno, maybe this is not a super-helpful update, b/c basically we stayed on 3.3.6 and are using ValueObject: { merge: true } in our type policies to fix it, but I nonetheless just wanted to post the FYI that 3.4.0 has a change in behavior here, such that we originally thought “oh this was fixed in 3.4.0-beta.4 and we should just upgrade” … but actually the real fix is to set merge: true, and hence the mystery/question if 3.4.0 caused this change/fix on purpose or not.

Thanks!

1reaction
miguelollercommented, Dec 14, 2020

@leethree, ah, thanks for pointing that out!

Read more comments on GitHub >

github_iconTop Results From Across the Web

document ids are not generated for nested objects
I expected channels will have its ids auto assigned. Here is code: public class Channel { public string Id { get; set; }...
Read more >
Best way to return count of nested objects without list them
Sure it's possible: DbContext.Categorias .Where(c => c.Id == id) .Select(c => new { // Assuming you also want the Categoria Categoria = c, ......
Read more >
Displaying Data from Related Objects Using Nested Data ...
You can retrieve data from any child object of a parent object using a data repeater that contains another data repeater, data table,...
Read more >
Nested field type | Elasticsearch Guide [8.5] | Elastic
Field data types ... The nested type is a specialised version of the object data type that allows arrays ... Elasticsearch has no...
Read more >
Tutorial: Querying nested data with Amazon Redshift Spectrum
SELECT c.id, c.name.given, c.name.family FROM spectrum.customers c;. The preceding query returns the following data.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found