apollo-client: Wrong response when parsed by apollo

I call graphql to get a membership table layout with the following query:

query ($ids: [String]!, $countryCode: String!) {
        flows(ids: $ids, countryCode: $countryCode) {
          table {
            id
            title
            blocks {
              name
              id
              plans {
                id
                    initial
                    level
                    media
                    name
                    recurring
                    recurring_period
                    trial_length
                    type
                    restrictions {
                      id
                      typeId
                      type
                      value
                }
              }
            }
          }
        }
      }

And when I look in the network tab I see this (which is correct): https://gist.github.com/charles-mathieus-jomedia/28ea24688c9163c364739a6e6b36bb1b TL;DR

flow: {
  tables: [
    {
      blocks: [
        { id: 'audiobooks', name: 'block1' },
        { id: 'all', name: 'block2' }
        { id: 'family', name: 'block3' }
      ]
    },
    {
      blocks: [
        { id: 'audiobooks', name: 'block4' },
        { id: 'all', name: 'block5' }
        { id: 'combo', name: 'block6' }
      ]
    },
  ]
}

Intended outcome:

I get the right blocks when I remove the ids from the block in the query: https://gist.github.com/charles-mathieus-jomedia/05c3de397de2ddcfadf06f5914035337 TL;DR

flow: {
  tables: [
    {
      blocks: [
        { id: 'audiobooks', name: 'block1' },
        { id: 'all', name: 'block2' }
        { id: 'family', name: 'block3' }
      ]
    },
    {
      blocks: [
        { id: 'audiobooks', name: 'block4' },
        { id: 'all', name: 'block5' }
        { id: 'combo', name: 'block6' }
      ]
    },
  ]
}

Actual outcome:

When I leave the ids, I get the wrong blocks: https://gist.github.com/charles-mathieus-jomedia/2d1e61404a944fb9a2618512056774d5 TL;DR

flow: {
  tables: [
    {
      blocks: [
        { id: 'audiobooks', name: 'block1' },
        { id: 'all', name: 'block2' }
        { id: 'family', name: 'block3' }
      ]
    },
    {
      blocks: [
        { id: 'audiobooks', name: 'block1' },
        { id: 'all', name: 'block2' }
        { id: 'combo', name: 'block6' }
      ]
    },
  ]
}

How to reproduce the issue:

Use the same id in different nested array of objects,

Version

  • apollo-client@2.0.4

Sorry in advance if this post is very long, but I didn’t filter the response to help you guys see what’s happening

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 24
  • Comments: 41 (7 by maintainers)

Most upvoted comments

Actually I was able to fix by setting fetchPolicy to no-cache. It’s confusing what the difference is between this and network-only which I was using before. I think it’s a terrible decision to have the default be neither of these. I was very confused without network-only set before I’d make graphql calls and they would just not return. Turns out they were cached…which I’d expect to still return data from the call! Worst part is the id field that is causing wrong data for me is not even defined as an ID type in my schema!!! It’s just a String that happens to be called id.

I have encountered the same bug, my current workaround/hack is to set a random ID for the affected types (effectivly turning off caching). Since dataIdFromObject only receives the object itself and not the whole “context” (like how it is nested) I dont see any option to do a better workaround.

const cache = new InMemoryCache({
  dataIdFromObject: object => {
    // FIXME: workaround buggy apollo cache, dont cache certain types at all!
    switch (object.__typename) {
      case 'Category':
        return Math.random()
      default:
        return defaultDataIdFromObject(object) // fall back to default handling
    }
  }
})

@hwillson (and the other maintainers) Also, I have extracted a minimal reproduction:

@eins78 Thanks for the reproduction. This isn’t a bug. Add __typename to the posts selection set in your query, and it will work:

query blogPostArchives {
  years {
    id
    name
    categories {
      id
      name
      posts {
        id
        title
        __typename
      }
    }
  }
}

When you don’t provide a __typename, Apollo’s in-memory cache uses a fallback method of associating data in the cache when it normalizes everything (you can read more about this here). This fallback isn’t foolproof and in your case when the data is normalized then attempted to be retrieved, the first set of posts will always be returned. But when you provide a __typename the cache is able to use that data to properly differentiate between the different posts, so when retrieving everything works properly.

Thanks for reporting this. There hasn’t been any activity here in quite some time, so we’ll close this issue for now. If this is still a problem (using a modern version of Apollo Client), please let us know. Thanks!

Also lost hours because of this issue 😦 This should not be closed!

I also recommend to check the following article about this issue https://kamranicus.com/posts/2018-03-06-graphql-apollo-object-caching

The solution with __typename doesn’t work for me. But this helped me:

import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';

const cache = new InMemoryCache({
  dataIdFromObject: object => {
    switch (object.__typename) {
      case 'foo': return object.key; // use `key` as the primary key
      case 'bar': return `bar:${object.blah}`; // use `bar` prefix and `blah` as the primary key
      default: return defaultDataIdFromObject(object); // fall back to default handling
    }
  }
});

It is also documented here: https://www.apollographql.com/docs/react/advanced/caching/#normalization

The bug is still present in current version of Apollo Client. It still leads to data corruption when used as documented.

I updated all packages to latest version in the reproduction: https://codesandbox.io/s/0y9w819z0n

Hi all - this issue is still closed as there isn’t a bug here. This is the way the Apollo Client cache works - by default it treats entities with the same __typename:id as being the same objects, when it splits things out for normalization. Looking at the graphql response from the repro:

{
  "data": {
    "years": [
      {
        "id": "y1",
        "name": "2000",
        "__typename": "Year",
        "categories": [
          {
            "id": "c1",
            "name": "cat 1",
            "__typename": "Category",
            "posts": [
              {
                "id": "p1",
                "title": "post 1",
                "__typename": "Post"
              },
              {
                "id": "p2",
                "title": "post 2",
                "__typename": "Post"
              }
            ]
          },
          {
            "id": "c2",
            "name": "cat 2",
            "__typename": "Category",
            "posts": [
              {
                "id": "p3",
                "title": "post 3",
                "__typename": "Post"
              },
              {
                "id": "p4",
                "title": "post 4",
                "__typename": "Post"
              }
            ]
          }
        ]
      },
      {
        "id": "y2",
        "name": "2001",
        "__typename": "Year",
        "categories": [
          {
            "id": "c1",
            "name": "cat 1",
            "__typename": "Category",
            "posts": [
              {
                "id": "p5",
                "title": "post 5",
                "__typename": "Post"
              },
              {
                "id": "p6",
                "title": "post 6",
                "__typename": "Post"
              }
            ]
          },
          {
            "id": "c2",
            "name": "cat 2",
            "__typename": "Category",
            "posts": [
              {
                "id": "p7",
                "title": "post 7",
                "__typename": "Post"
              },
              {
                "id": "p8",
                "title": "post 8",
                "__typename": "Post"
              }
            ]
          }
        ]
      }
    ]
  }
}

Even though the categories are returned under 2 different years, they are still using the same id’s of c1 and c2. When Apollo Client splits this response up to store in its cache, it splits out all of the entities by __typename and id. So when things are stored in the cache, they look like this:

Image 2020-08-03 at 2 39 53 PM png

The c1 and c2 categories are only stored once, since they are seen as being the same entities due to their __typename:id.

That being said, if you don’t want this to happen and you’re using Apollo Client 3, you can disable normalization for Category types. This will make sure each category is seen as being independent, and is stored under each year. As a quick example, if you change the repro to include the following type policy:

const client = new ApolloClient({
  uri: "https://l72oyj30r7.sse.codesandbox.io/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      Category: {
        keyFields: false
      }
    }
  })
});

and re-run the repro and check the cache, you’ll now see:

Image 2020-08-03 at 2 48 14 PM png

Categories are no longer normalized, which means the duplicate categories exist under their associated years. You’ll see that the posts are still normalized, with p1 to p8 being split out as their own entities.

Hopefully this helps clear things up. Thanks!

Thanks @charles-mathieus-jomedia ! Absolutely same issue just got me really hard. When I query my graphQL server results are perfect. But it seems that Apollo just takes prev values from cache and inserts them for every similar id. Someone explain the issue please.

I ran into this bug as well. When an item nested under one entity changed, the same item (same id) nested under a different entity would change as well when it was not supposed to.

Our workaround was to overwrite the original id with a unique ID on the incoming data (combined the entity id and original id in this case), then re-instate the original ID when passing it back to the database to be updated. Hacky, but works.

@hwillson No, adding __typename does not fix it: https://0y9w819z0n.codesandbox.io/

The problem (seemingly) is the way the data is nested: the Posts are nested inside a Category, those are nested inside Year. The cache then considers all Categories of the same ID “equal” and thus returns the same list of Posts. But, because Category is nested in a Year, they are not actually the same. Yes, this is a weird data structure, but its allowed by the spec and it resulted from a real-world use case. As stated above, the only workaround we’ve found is to build a special ID just for the apollo cache, so a Category would have a “cacheKey” of ${YearID}--${category}.

I just ran into this issue.

  • Network tab: { ..., id: 'some-id', isPrimary: true }.
  • Actual code: { ..., id: 'some-id', isPrimary: null }.

Following the tip from @simplychaos to change fetchPolicy from network-only to no-cache solves my problem.

For reference, I am using apollo-angular and InMemoryCache.

When query field id rename can fix it. try it. @charles-mathieus-jomedia

like it uid:id

query ($ids: [String]!, $countryCode: String!) {
        flows(ids: $ids, countryCode: $countryCode) {
          table {
            uid:id
            title
            blocks {
              name
              uid:id
              plans {
                uid:id
                    initial
                    level
                    media
                    name
                    recurring
                    recurring_period
                    trial_length
                    type
                    restrictions {
                      uid:id
                      typeId
                      type
                      value
                }
              }
            }
          }
        }
      }

Same problem for me. There should be a way to tell apollo client that the query response must be exactly as the network request data response. This is very frustrating.

I’m also facing this same problem.

Hello All , is there any solution for this issue? i am using apollo 2.6 and still see the same issue mentioned above.

As stated above, the only workaround we’ve found is to build a special ID just for the apollo cache, so a Category would have a “cacheKey” of ${YearID}--${category}.

Because this is a very confusing thing to explain, I forked the reproduction and added the workaround from our app: server: https://codesandbox.io/s/apollonestedcachingbug-u46hq client: https://codesandbox.io/s/basic-apollo-app-etgv0

It does not seem like this bug is actively worked on, but in any case I migrated the endpoint from my example above from (deprecated) apollo launchpad to codesandbox. I upgraded all dependencies and confirmed it is still broken.