Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch policy no-cache returns null data #3030

Closed
razor-x opened this issue Feb 16, 2018 · 34 comments · Fixed by #3102
Closed

Fetch policy no-cache returns null data #3030

razor-x opened this issue Feb 16, 2018 · 34 comments · Fixed by #3102
Labels

Comments

@razor-x
Copy link

razor-x commented Feb 16, 2018

Intended outcome:

Run a query and receive non-null data when using fetchPolicy: 'no-cache'. Tested on Node and in Browser.

client.query({query, fetchPolicy: 'no-cache'})

Actual outcome:

The data property is null:

{
  data: null
  loading: false
  networkStatus: 7
  stale: true
}

How to reproduce the issue:

Node.js

  1. Visit this runkit and fork it: https://runkit.com/razorx/apollo-client-no-cache/4.0.1
  2. Run the first box to initialize.
  3. Run the second box to run a query with the default fetch policy: the Promise will eventually resolve with good data (data: {...} ). (expected)
  4. Run the second box to run a query with the no-cache fetch policy: the Promise will eventually resolve with no data (data: null). (unexpected)
  5. Run the second box to run a query with the network-only fetch policy: the Promise will eventually resolve with good data. (expected)

Browser

  1. Visit https://codesandbox.io/s/znx9n9x943
  2. Run the code with the "refresh" button in the right pane.
    • clientA works with default fetch policy: returns non-null data. (expected)
    • clientB does not work with no-cache fetch policy: returns null data. (unexpected)
    • clientC works with default fetch policy (returns non-null data), then the query is repeated again with clientC, this time with no-cache fetch policy, and the data returns non-null! (what?)

Version

  • apollo-client@2.2.3

Contents of the runkit for reference

require('isomorphic-fetch')
require('graphql')
const { ApolloClient } = require('apollo-client')
const { HttpLink } = require('apollo-link-http')
const { InMemoryCache } = require('apollo-cache-inmemory')
const gql = require('graphql-tag')

const uri = 'https://graphql-pokemon.now.sh/graphql'

const query = gql`{
  pokemon(name: "Pikachu") {
    id
    number
    name
  }
}`

const client = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
})

client.query({query})
  .then(data => console.log(data))
  .catch(error => console.error(error))
  
// => gives good data
  
 const clientNoCache = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
})

clientNoCache.query({query, fetchPolicy: 'no-cache'})
  .then(data => console.log(data))
  .catch(error => console.error(error))
  
// => gives NULL data

const clientNetworkOnly = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
})

clientNoCache.query({query, fetchPolicy: 'network-only'})
  .then(data => console.log(data))
  .catch(error => console.error(error))
  
// => gives good data

Contents of the browser example for reference

import { gql, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';
import App from './App';

const uri = 'https://qkv3mq9rp.lp.gql.zone/graphql'

const clientA = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
});

const clientB = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
});

const clientC = new ApolloClient({
  link: new HttpLink({uri}),
  cache: new InMemoryCache()
});

const query = gql`{
  people {
    id
    name
  }
}
`

clientA.query({query})
  .then(data => document.write('A cache<br \><br \>' + JSON.stringify(data)))
  .then(() => clientB.query({query, fetchPolicy: 'no-cache'}))
  .then(data => document.write('<br \><br \>B no-cache<br \><br \>' + JSON.stringify(data)))
  .then(() => clientC.query({query}))
  .then(data => document.write('<br \><br \>C cache<br \><br \>' + JSON.stringify(data)))
  .then(() => clientC.query({query, fetchPolicy: 'no-cache'}))
  .then(data => document.write('<br \><br \>C no-cache<br \><br \>' + JSON.stringify(data)))
  .catch(error => console.error(error))
@dennislaupman
Copy link

Thanks for detailed report @razor-x. I have the same issue :)

@razor-x
Copy link
Author

razor-x commented Feb 20, 2018

@rschef I need this to work in a system which doesn't know about react and uses this client directly, so unfortunately that workaround won't be an option for me.

I poked around a little in QueryManager.ts but I couldn't find an obvious reason for this bug.

I'm hoping someone more familiar with the code will be able to prioritize and find this quickly. I would consider this a serious bug as I could rephrase the issue as apollo-client does not support the most basic GraphQL operation: running a query and returning the correct result (without the added complexity of caching).

@kamranayub
Copy link
Contributor

kamranayub commented Mar 1, 2018

It's this code:

if (fetchPolicy !== 'no-cache') {
try {
this.dataStore.markQueryResult(
result,
document,
variables,
fetchMoreForQueryId,
errorPolicy === 'ignore' || errorPolicy === 'all',
);
} catch (e) {
reject(e);
return;
}
}
this.queryStore.markQueryResult(
queryId,
result,
fetchMoreForQueryId,
);
this.invalidate(true, queryId, fetchMoreForQueryId);
this.broadcastQueries();
}
if (result.errors && errorPolicy === 'none') {
reject(
new ApolloError({
graphQLErrors: result.errors,
}),
);
return;
} else if (errorPolicy === 'all') {
errorsFromStore = result.errors;
}
if (fetchMoreForQueryId) {
// We don't write fetchMore results to the store because this would overwrite
// the original result in case an @connection directive is used.
resultFromStore = result.data;
} else {
try {
// ensure result is combined with data already in store
resultFromStore = this.dataStore.getCache().read({
variables,
query: document,
optimistic: false,
});
// this will throw an error if there are missing fields in
// the results which can happen with errors from the server.
// tslint:disable-next-line
} catch (e) {}
}

It was introduced in #2934

Basically, if fetchMoreForQueryId is not present, it tries to get the result from the cache even though it was never set (right above, if (fetchPolicy !== "no-cache")).

The branch needs to be updated to use result.data if no-cache is used, I think:

if (fetchMoreForQueryId || fetchPolicy === "no-cache")

Not sure what implications that has.

I'm running into this right now because the cache actually seems to be causing performance issues in our use case and I wanted to try bypassing it; but I can't due to this bug :( If I can figure out how to develop this locally, I could try updating it.

@jbaxleyiii Should I try to update the branch as I mentioned to fix this issue? Do you see any issues with that approach?

@kamranayub
Copy link
Contributor

kamranayub commented Mar 1, 2018

Alright, I am debugging this issue actively.

I think my "fix" will work, however there's another problem.

In QueryManager, the queryListenerForObserver will check the lastResult of the ObservableQuery. When using anything other than no-cache fetchPolicy, the above code will write to the data store. Then, right here:

const readResult = this.dataStore.getCache().diff({

If lastResult ends up being null/undefined, the code assumes the data exists in the store and uses that. Hence, even with my fix, it doesn't work because queryListenerForObserver will call observer.next() with the null data before fetchRequest calls complete and resolve with the actual data from the network request.

I'm a little disappointed, because a simple test showcases this bug.

it('supports no-cache fetchPolicy query', () => {
    const nonCachedQuery = gql`
      query nonCachedQuery {
        luke: people_one(id: 1) {
          name
        }
      }
    `;

    const data1 = {
      luke: {
        name: 'Luke Skywalker',
      },
    };

    const queryManager = mockQueryManager({
      request: { query: nonCachedQuery },
      result: { data: data1 },
    });

    return queryManager.query<any>({
      query: nonCachedQuery,
      fetchPolicy: 'no-cache',
    }).then(result => {
      expect(result.data['luke'].name).toBe('Luke Skywalker');
    });
  });

  it('supports no-cache fetchPolicy watchQuery', () => {
    const nonCachedQuery = gql`
      query nonCachedQuery {
        luke: people_one(id: 1) {
          name
        }
      }
    `;

    const data1 = {
      luke: {
        name: 'Luke Skywalker',
      },
    };

    const queryManager = mockQueryManager({
      request: { query: nonCachedQuery },
      result: { data: data1 },
    });

    return queryManager.watchQuery<any>({
      query: nonCachedQuery,
      fetchPolicy: 'no-cache',
    }).result().then(result => {
      console.log(result);
      expect(result.data['luke'].name).toBe('Luke Skywalker');
    });
  });

Now I'm trying to figure out the best way to essentially get the data in a place queryListenerForObserver can access it that is not the store--or figure out a way to call observer.next some other way. I'm not well equipped to figure that out but I'll keep trying.

@kamranayub
Copy link
Contributor

I have the tests passing, I just have it use setQuery to set the newData object manually if using no-cache. I'll begin a PR and I will also test locally to see if it's addressing my issue...

kamranayub added a commit to kamranayub/apollo-client that referenced this issue Mar 6, 2018
kamranayub added a commit to kamranayub/apollo-client that referenced this issue Mar 16, 2018
jbaxleyiii pushed a commit to kamranayub/apollo-client that referenced this issue Mar 23, 2018
jbaxleyiii pushed a commit that referenced this issue Mar 23, 2018
* Set query newData if using no-cache fetch policy and set resultFromStore

* Add tests for no-cache fetch policy

* Get VSCode to run jest for apollo-client for debugging

* Update authors/changelog

* Move tests to appropriate location

* Pass through no-cache fetch policy
@mshwery
Copy link

mshwery commented Apr 13, 2018

I'm getting an eerily similar issue (same data shape, despite getting a valid gql response, default fetch policy), but only when building minified production assets for the apollo-client. That leads me to believe it's something in our code, but I'm not sure if any apollo libs look for NODE_ENV=production.

@AndrewPrifer
Copy link

Any updates on this?

@sreuter
Copy link

sreuter commented Aug 2, 2018

This makes things pretty much unusable for me in some cases. I tried no-cache to actually get a proper loading indicator, as with the default fetch policy loading sometimes returns false even tho data is being loaded from remote. Now I get a proper loading indicator, but data is undefined (not null as others report) when loading finished. Changing things back to the default fetch-policy gives me the data, but again a misleading (false) loading state. 🥜

@hwillson hwillson added no-cache and removed no-cache labels Aug 2, 2018
@audiolion
Copy link

This was fixed in release 2.3.8 by hwillson

@HRK44
Copy link

HRK44 commented Oct 17, 2018

I'm still having this issue (data is {}) on 2.4.2

quazzie added a commit to quazzie/apollo-client that referenced this issue Oct 24, 2018
Is there a reason why returnPartialData is true?
This was hiding an error and returning { data: undefined, networkState: 7, stale: true } for me.
I had forgotten to query for the id of an object and watchQuery could not find it in store.
With returnPartialData false i was able to get an error about missing property and finally find the problem.
With this true all i was getting from watchQuery was { data: undefined, networkState: 7, stale: true }.

Maybe related issues: apollographql#3030 , apollographql#2914
@AdelBachene
Copy link

AdelBachene commented Nov 8, 2018

I'm still having this issue with the latest version 2.4.5

"apollo-client": { "version": "2.4.5", "requires": { "@types/async": "2.0.50", "@types/zen-observable": "^0.8.0", "apollo-cache": "1.1.20", "apollo-link": "^1.0.0", "apollo-link-dedup": "^1.0.0", "apollo-utilities": "1.0.25", "symbol-observable": "^1.0.2", "zen-observable": "^0.8.0" },

output is:

 queryObj.context = this.getContext();
        this.graphqlClient.authClient.query(
            queryObj
        ).then(data => {
{
data: null
loading: false
networkStatus: 7
stale: true
}

but I could see the results in the network section in my browser and I could get non null data only If I add this to my apollo client:

             ,defaultOptions: {
                    query: {
                        fetchPolicy: "no-cache"
                    }
                }

@thanpolas
Copy link

@kamranayub this issue still persists randomly.

From what we could gather, for no apparent reason, once every few times (less than 10 in our case) the Query component will trigger twice when fetchPolicy is set to no-cache. The first invocation will have the data, the second invocation will not.

We have replaced all our "no-cache" policies with "network-only" and will continue monitoring.

Please re-open this issue or let me know if I need to open a new one.

@olistic
Copy link

olistic commented Nov 29, 2018

I have the same issue as @thanpolas, also happening erratically. Running version 2.4.7 of apollo-client. If I console.log the networkStatus, I see that in the cases where it breaks it logs:

networkStatus = 1
networkStatus = 7
networkStatus = 7

instead of the expected:

networkStatus = 1
networkStatus = 7

@signal-intrusion
Copy link

Confirming that this is still an issue. We're encountering this when mocking network requests during E2E tests with Cypress.

@jdorleans
Copy link

jdorleans commented Dec 19, 2018

After several trails I figured data is always null whenever there are missing fields from the projected result. For instance, if we're supposed to query an user like: user { uuid email picture } and for some reason the server does not return field picture, then result will be:

{
  data: null
  loading: false
  networkStatus: 7
  stale: true
}

I've also noticed we'll get a Missing field picture warning on the console.

In my particular case, my backend never serializes null values. Since user's picture and many other fields are optional (nullable), we constantly face this problem.

Does anyone have an idea why those missing fields break the result causing this issue?

@gregorskii
Copy link

gregorskii commented Dec 19, 2018

Seeing something that could be related to this but do not have the ability to reproduce ATM. We have a query that is inside of a component that is not being re-rendered (checked with consoles on render()) but the context provider for the query returns null for data.

Using partialRefretch: true seems to help, but results in the data being null for a short period. While the data goes missing there is no graphql call in the console.

@mariozig
Copy link

mariozig commented Jan 8, 2019

We encountered this issue and resolved via insight from @jdorleans's comment. When the response does not include a field you're expecting the entire data object will be null.

@jdorleans
Copy link

I forgot to mentioned I created a new issue (#4267) to keep track of the problem I'm facing since it might differ from this one here.

@standy
Copy link

standy commented Jan 31, 2019

Have same issue on apollo-client@2.4.12, why this is closed?

@v-i-n-y-a
Copy link

The same problem with the Query component. It returns normal data once, then data = {} with the same query (nothing is changed just component is rerendered) when fetchPolicy is 'no-cache'

@rivertam
Copy link

rivertam commented Feb 21, 2019

Ironically, this happens for me when my policy is "network-only". Switching to "no-cache" (what's the difference, by the way? both are making the network request anyways) fixes it.

@audiolion
Copy link

@rivertam are you using the client and setting "network-only" or the Query component?

@rivertam
Copy link

@audiolion Client in this case

@below-1
Copy link

below-1 commented Mar 2, 2019

@rivertam Same here. Using Apollo Client, switch to "no-cache" solve the problem

@fikip
Copy link

fikip commented Apr 1, 2019

I'm actually experiencing this issue from multiple sides.
In some cases, the query works with "network-only" but does not work with "no-cache", whereas, in other queries, "network-only" does not work, but "no-cache" does.
I've tried to see whether my issue might be related to the missing fields @jdorleans mentioned, but that does not seem to be the case.
Oh, and data indeed does seem to be undefined instead of null, as @sreuter mentioned.
Somehow, reloading the whole page does seem to solve the issue & everything is fetched properly.

Why is this closed again? There's not even a workaround, save an actual fix for this.

@andfs
Copy link

andfs commented Apr 16, 2019

I'm with the same problem that @jdorleans.
Apollo is a great library, but I'm facing a lot of bugs :(

@harveyconnor
Copy link

Still having the same issues as @bozskejkaja
Neither no-cache or network-only are working for me when making @client side queries.

@marticrespi
Copy link

I still having the same issues with apollo-client@2.5.1. It only works the first query, if you request the same data always is empty, if you change some fields it works only the first request.

@dwalintukan
Copy link

dwalintukan commented May 21, 2019

no-cache solved the problem for me on 2.5.1

@MM3y3r
Copy link

MM3y3r commented Jun 13, 2019

Same issue.

@andfs
Copy link

andfs commented Jun 14, 2019

All my problems were solved with errorPolicy: "all"

const client = new ApolloClient({
      cache: new InMemoryCache(),
      link: errorLink.concat(link),
      resolvers: {},
      defaultOptions: {
        query: {
          errorPolicy: "all"
        }
      }
    });

@saqib-ahmed
Copy link

After several trails I figured data is always null whenever there are missing fields from the projected result. For instance, if we're supposed to query an user like: user { uuid email picture } and for some reason the server does not return field picture, then result will be:

{
  data: null
  loading: false
  networkStatus: 7
  stale: true
}

I've also noticed we'll get a Missing field picture warning on the console.

In my particular case, my backend never serializes null values. Since user's picture and many other fields are optional (nullable), we constantly face this problem.

Does anyone have an idea why those missing fields break the result causing this issue?

This insight from @jdorleans resolved the issue for me.

@Platekun
Copy link

@jdorleans You have no idea how many hours you saved me. I was going in a rabbit hole trying to find the answer. Thank you

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.