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

Infinite network requests with 'cache-and-network' fetch policy #9450

Closed
LinusU opened this issue Feb 24, 2022 · 7 comments
Closed

Infinite network requests with 'cache-and-network' fetch policy #9450

LinusU opened this issue Feb 24, 2022 · 7 comments

Comments

@LinusU
Copy link

LinusU commented Feb 24, 2022

This took us quite a while to track down 😅

It's a combination of:

  1. Using useQuery with same query in multiple places (don't think it has to be same query, but with field overlap)
  2. Using fetchPolicy: 'cache-and-network'
  3. Having a field which is always updated from the server, in our case a lastOnlineAt field

See below for a complete test case!

Intended outcome:
I did not intend for infinite network requests to be sent to my server.

Actual outcome:
The client sends a new request as soon as the last query has finished to receive a response from the server.

How to reproduce the issue:

  1. mkdir use-query-infinite-loop
  2. cd use-query-infinite-loop
  3. npx create-react-app@latest .
  4. Edit index.js to contain:
    import { gql, useQuery, ApolloClient, InMemoryCache, ApolloProvider, } from '@apollo/client'
    import React, { useState } from 'react'
    import ReactDOM from 'react-dom'
    
    const TEST = gql`
      query {
        foobar {
          id
          lastOnlineAt
        }
      }
    `
    
    function Test() {
      const { loading } = useQuery(TEST, { fetchPolicy: 'cache-and-network' })
    
      return <div>loading = {loading ? 'true' : 'false'}</div>
    }
    
    function App() {
      const [count, setCount] = useState(1)
    
      return (
        <div>
          {Array.from({ length: count }).map((_, i) => <Test key={i} />)}
    
          <button onClick={() => setCount(count + 1)}>Add</button>
        </div>
      )
    }
    
    const client = new ApolloClient({
      uri: 'https://o5xgyw.sse.codesandbox.io',
      cache: new InMemoryCache()
    })
    
    ReactDOM.render(
      <ApolloProvider client={client}>
        <App />
      </ApolloProvider>,
      document.getElementById('root')
    )
  5. npm start
  6. Press "Add" and observe the loading state
  7. Open the network inspector and see infinite requests
Screen.Recording.2022-02-24.at.16.39.05.mov

This is the code used to run the server at https://o5xgyw.sse.codesandbox.io:

const { ApolloServer, gql } = require("apollo-server");

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Foobar {
    id: ID!

    lastOnlineAt: String
  }

  type Query {
    foobar: Foobar
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    foobar: () => ({
      id: "foobar",
      lastOnlineAt: new Date().toISOString()
    })
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

Versions

  System:
    OS: macOS 12.0.1
  Binaries:
    Node: 17.3.1 - /opt/homebrew/bin/node
    Yarn: 1.22.17 - /opt/homebrew/bin/yarn
    npm: 8.3.0 - /opt/homebrew/bin/npm
  Browsers:
    Safari: 15.1
  npmPackages:
    @apollo/client: ^3.5.9 => 3.5.9 
@ertrzyiks
Copy link

ertrzyiks commented Mar 1, 2022

I independently hit the same problem and created a sandbox with repro steps. It uses the default fetch policy and is based on two queries to the same type that is cached in the denormalized way (no id, no key fields).

https://codesandbox.io/s/github/ertrzyiks/apollo-overfetching-example/tree/main/

By default it just duplicates the first query but it ends up in the infinite loop if you update client.ts

{ id: 1, name: "HTML", color: "blue", articlesCount: 10 }

to

{ id: 1, name: "HTML" + Math.random(), color: "blue", articlesCount: 10 }

I narrowed it down to different handling of the server response in the apollo core QueryInfo. I didn't dive deeper, but I found a related comment in another issue: #6760 (comment)

Anyway, if the second request for the same query returns different data than the first one (simulated with Math.random()) we end up in the infinite loop of requests. If the responses match, it ends on just one additional query being made.

@tomas-c
Copy link

tomas-c commented Apr 21, 2022

I believe I'm hitting a similar problem.

I have two different queries with overlapping data. One using "network-only" and the other using "cache-and-network" fetch policies.

Similarly as described in both issues above, the queries return different results if queried multiple times. The results differ because they contains lists and the list sorting on the backend in not stable.

@ghost
Copy link

ghost commented Apr 28, 2022

We've had this problem for over two years now. It might get resolved some day but until then I'm advising everyone I work with to use alternatives to apollo-client.

How we've been working around this is simply to stop returning errors from the server.
User is not authenticated? Return an empty list instead of throwing a 401. It's horrible but that's the only way we've been able to work with this bug present.

@jpvajda
Copy link
Contributor

jpvajda commented Oct 12, 2022

We heard from a customer in Mid Sept that this was also a problem they were having. here is what they said:

The query actually resolves but then restarts. Every time the query resolves a second or 2 later it starts again. However, I seem to have 2 cases triggering this repetition.

The first only does this when the object being returned is a large set of data. The fetchPolicy is set to "cache-first" but has no problems with smaller sets of returned data.

The second is a case where 2 hooks (for example useGetDataQuerySubsetOne and useGetDataQuerySubsetTwo) are calling the same graph query (for example query getDataQuery) but each returns different bits of information. When these utilize fetchPolicy: "network-only" they work just fine but when I change it to fetchPolicy: "cache-first" they start entering the fetch resolve (notice this triggers with a successful resolution rather than it being an issue with caching an error) > repeat loop back to back, and subsequently repeatedly triggering child queries that depend of data from each of them. We also do not yet have a typePolicy defined for either of these queries, if that helps.

If my understanding is correct I should be able to reproduce it, I'll attempt to submit something asap, but I wanted to provide a further verbose explanation just in case.

I am checking which version Client they are on an asking them to upgrade to 3.7, which is our latest.

@jpvajda jpvajda added 🐞 bug 🔍 investigate Investigate further labels Oct 12, 2022
@jpvajda jpvajda added this to the 3.7.x patch releases milestone Oct 12, 2022
@bignimbus
Copy link
Contributor

bignimbus commented Oct 12, 2022

@jpvajda When I forked this codesandbox in #9450 (comment) and upgraded to Apollo Client 3.7.0 it looks like the same issue persists

@bignimbus bignimbus self-assigned this Oct 13, 2022
@bignimbus
Copy link
Contributor

Sharing some findings. This issue seemed reminiscent of a bug that @alessbell is working on fixing here: #10143. I thought it might be interesting to see if the problem described in this Issue would still be present in the latest revision on that branch. If I'm looking at this correctly, that branch appears to resolve this bug. You can check it out yourself in this repo I made based on @LinusU's original comment: https://github.com/bignimbus/use-query-infinite-loop. Installation instructions are a bit complicated, see the README. Screencapture below:

Screen.Recording.2022-10-13.at.1.05.50.AM.mov

@alessbell
Copy link
Contributor

alessbell commented Oct 24, 2022

The original issue here was resolved as of 3.6.1 (changelog) via #9504.

@tomas-c @stijn-gravity please try versions >= 3.6.1 (latest is 3.7.1) to see if it solves your issues as well :)

@ertrzyiks I have not yet isolated the issue you are seeing, forking your codesandbox and testing with the latest 3.7.1 does not resolve the issue as @bignimbus noted above. Would you mind creating a separate tracking issue for the problem you're seeing? Or feel free to continue discussion on #6760 which you linked to.

Thanks all!

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

No branches or pull requests

6 participants