React-apollo: <Query> can sometimes provide partial query result data in render() when networkStatus is 1

Created on 17 Aug 2018  路  7Comments  路  Source: apollographql/react-apollo

This is an odd bug, and I couldn't find any issues specifically related to it that are currently open. This is a tough one to reproduce as well, but we were going through an upgrade to 2.1.9 where one of our components reliably had this problem every single time.

The bug is basically that <Query> will render children(queryResult) where that queryResult is a currently fetching query, that has some fields already in the InMemoryCache. Below, I'll try and set up the component structure so you can see how this happens, and the general call trace that leads to it happening.

Reproduction Setup

  • Component A is a parent of component B, both are using <Query> to render their own GQL queries
  • B's query has all of the fields of A, plus some extra. Effectively B's query is a superset of A's.
  • fetchPolicy is not defined, so both queries use the default cache-only
  • No other options, or prop mapping is used - just the stock <Query>.

The gql for both are structured like so:

query A {
  user {
    id
    name
  }
}

query B {
  user {
    id
    name
    birthDay
  }
}

Intended outcome:

In render(), I expect that the data argument used in B's <Query> render function to have loading set to true, and to not have a user field until loading switches to false and B's query is fully satisfied.

Actual outcome:

data looks like so:

{
  loading: true,
  networkStatus: 1,
  user: {
    id: '1',
    name: 'Bob',
  },
  ...
}

As you can see, the data prop is returning partially cached data for the query. This makes it hard to guarantee when any of the actual data props are safe to use short of just defaulting to rendering nothing when loading. This is not always an optimal UX. Being able to safely use data.user when it is there AND loading is true will prevent devs from having to check networkStatus and all subfields throughout the query to be able to make safe assumptions about the shape of their query result.

Call Trace

Basically what's happening is this:

  • A is mounting B
  • B: componentDidMount() is called first, and its query begins fetching
  • A: componentDidMount() is called, and its query begins fetching
  • A: the query is fulfilled first, and after serveral subscription's next calls, will eventually call this.updateCurrentData() inside of the mounted <Query>
  • B: this triggers a render cycle for B since it is a child of A, where calling this.getQueryResult() inside of render() results in partially-filled data
  • B: children(queryResult) is called with the partially-filled and still loading result.

Version

Most helpful comment

This exact scenario still happens for me with [email protected]

All 7 comments

I've also had something similar happen

query listRepositories($offset: Int) {
  allRepositoriesList(orderBy: CREATED_AT_DESC, first: 20, offset: $offset) {
    createdAt
    id
    name
    refId
    updatedAt
  }
}

then when I want to use fetch more I have to check if allRepositoriesList is an array even though, by default it SHOULD be an array, but, its an object.

return fetchMore({
  variables: {
    offset: (data.allRepositoriesList || []).length // definitely a bug
  },
  updateQuery: (prev, { fetchMoreResult }) =>
    !fetchMoreResult
      ? prev
      : {
          ...prev,
          allRepositoriesList: [
            ...prev.allRepositoriesList,
            ...fetchMoreResult.allRepositoriesList
          ]
        }
});

and when I pull data out of the query, I'm doing it as follows so even the default is an Array. Apollo is doing something crazy.

{({data = {allRepositoriesList: []}) => ...}

I have seen this too and when I use cachePolicy: "cache-and-network I can reproduce it every time in our app. In my case this happens when I use apollo-link-state and nested content:

query Query {
   parent @client {
      id
      name
      child @client {
         id
         name
      }
   }
} 

If parent is already in the cache the query returns parent object without child field. If I change the fetchPolicy to cache-first it returns data correctly (after it has received child data). I can't reproduce this if I just try to render the query component. I think @benjaminsaurusrex explanation is exactly what causes this.

Here is a small repro case: https://codesandbox.io/s/wn1v90qjl7

I have an understanding that query should never return partial data.

@jsslai I can confirm that apollographql/apollo-client#3956 _will_ fix the issue. I even have a unit test case for <Query /> that proves so. I will open a PR with the test case as soon as the next release of apollo-cache-inmemory is cut. Hopefully that happens soon as this is a very irksome issue!

Awesome, I'll close this issue then. 馃憤

This exact scenario still happens for me with [email protected]

Was this page helpful?
0 / 5 - 0 ratings