React-apollo: Empty data object given very specific query results

Created on 3 Apr 2019  路  2Comments  路  Source: apollographql/react-apollo

I have a query that returns data but results in an empty data object when using react-apollo.
The query contains 4 entities and if one of them is removed the resulting data object becomes normal.

I have replicated the issue in this simple sandbox https://codesandbox.io/embed/54rzlpzz9p
Try removing any entity from the query to data being populated.

P.S
I believe this issue is quite different from the related "empty data" issues

Most helpful comment

@timuric This is functioning as designed. Looking at your query:

query ProductDetails($id: ID!) {
  product(id: $id) {
    productType {
      productAttributes {
        id
        values {
          id
        }
      }
    }
    attributes {
      attribute {
        id
        values {
          slug
        }
      }
    }
  }
}

The first object returned in your productAttributes array, and the object returned by attributes, both have a __typename of Attribute and the same id of QXR0cmlidXRlOjEz, coming from your backend. This means as far as Apollo Client is concerned they are the same object. When they are stored in the cache, InMemoryCache normalization fires and stores them as one entity. Looking further at the data coming from your backend, the child values { id } and values { slug } selection sets also return the same objects, so it stands to reason that they can also be stored together when cache normalization fires. Since you haven't specified an id field in your second values { slug } selection set however, InMemoryCache creates an id; that id doesn't match the id used in the first values { id } selection set. So long story short, even though those values are really the same, InMemoryCache thinks they are different, and stores them separately in the cache. Then when future queries fire and try to pull data back out of the cache to fulfill the full query, those queries only find the values returned from the first values { id } selection set (which was properly associated with the normalized parent Attribute objects in the cache). Those results don't include the slug field though, so you've then hit what is known as a cache over-fetch issue - you're asking for fields that the cache doesn't know how to resolve, so it gives you an empty object back.

The good news is that to fix things you just have to add the id field to your second values selection set:

query ProductDetails($id: ID!) {
  product(id: $id) {
    productType {
      productAttributes {
        id
        values {
          id
        }
      }
    }
    attributes {
      attribute {
        id
        values {
          id
          slug
        }
      }
    }
  }
}

All 2 comments

@timuric This is functioning as designed. Looking at your query:

query ProductDetails($id: ID!) {
  product(id: $id) {
    productType {
      productAttributes {
        id
        values {
          id
        }
      }
    }
    attributes {
      attribute {
        id
        values {
          slug
        }
      }
    }
  }
}

The first object returned in your productAttributes array, and the object returned by attributes, both have a __typename of Attribute and the same id of QXR0cmlidXRlOjEz, coming from your backend. This means as far as Apollo Client is concerned they are the same object. When they are stored in the cache, InMemoryCache normalization fires and stores them as one entity. Looking further at the data coming from your backend, the child values { id } and values { slug } selection sets also return the same objects, so it stands to reason that they can also be stored together when cache normalization fires. Since you haven't specified an id field in your second values { slug } selection set however, InMemoryCache creates an id; that id doesn't match the id used in the first values { id } selection set. So long story short, even though those values are really the same, InMemoryCache thinks they are different, and stores them separately in the cache. Then when future queries fire and try to pull data back out of the cache to fulfill the full query, those queries only find the values returned from the first values { id } selection set (which was properly associated with the normalized parent Attribute objects in the cache). Those results don't include the slug field though, so you've then hit what is known as a cache over-fetch issue - you're asking for fields that the cache doesn't know how to resolve, so it gives you an empty object back.

The good news is that to fix things you just have to add the id field to your second values selection set:

query ProductDetails($id: ID!) {
  product(id: $id) {
    productType {
      productAttributes {
        id
        values {
          id
        }
      }
    }
    attributes {
      attribute {
        id
        values {
          id
          slug
        }
      }
    }
  }
}

This is kind of late but thanks @hwillson for that nice explanation and solution to this.

Was this page helpful?
0 / 5 - 0 ratings