apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.38k stars 2.66k forks source link

resetStore: Store reset while query was in flight. #2919

Closed joenoon closed 5 years ago

joenoon commented 6 years ago

Intended outcome:

resetStore without errors being thrown.

https://github.com/apollographql/apollo-client/blob/b354cf07e979def7aa0219e60dca63e8a33fa822/packages/apollo-client/src/core/QueryManager.ts#L787..L796

Actual outcome:

I'm running into an issue when calling resetStore after a logout scenario. I think I understand the reasoning, but in my case I can't seem to find a workable way around it, and something else odd is happening:

fetchQueryPromises in snippet above is a Map, with values of {promise,reject,resolve}, so I look inside to see what they are:

const promises = Array.from(this.fetchQueryPromises.values()).map(x => x.promise)

My plan was to await Promise.all(promises) before calling resetStore, however, promises is [undefined]. For some reason, an entry is added to fetchQueryPromises without a promise, so I can't wait for it before resetting.

The actual error Store reset while query was in flight. looks like it might not actually cause a problem, which was my main concern. But it seems like there should be a way to avoid an error being thrown.

Waiting for existing promises to complete before reset, or exposing a function to allow the user to await them before resetting seems right to me.

Is there a case for calling resetStore where a thrown error is the right outcome?

Version

Poincare commented 6 years ago

The point of calling resetStore is you basically want to flush out information from the store and bring it to a clearly known state. If you have queries in flight and one of them comes back, writing the result to the cache may either cause an error (leading to a broken UI component) or lead to a state that's not expected by the application developer after calling resetStore (e.g. may leak PII).

I think throwing an error on having an inflight query is actually the intended outcome here. However, the part where you're seeing [undefined] for promises feels like a bug. Could you write a quick unit test that produces this behavior and we can then work on getting it fixed?

ahalimkara commented 6 years ago

Is it possible to display the query name causing this error? Error message is not helpful, it is difficult to fix this error.

joenoon commented 6 years ago

@Poincare If no one else has noticed the undefined promise, then it's probably something specific to my app which is using vue-apollo. I'll try to think up how I could repro this in isolation, but not much comes to mind.

I'm still not clear why throwing an error is the intended outcome the way it seems to work now.

I could see if there was a way to handle the error, but aren't the promises getting rejected just floating around internally in apollo-client and detached from userland-access?

Or I could see if there was a way to first stop inflight queries purposely, and then safely call resetStore.

But I'm just not wrapping my head around calling something that may or may not throw an exception with no way to catch it. If I'm missing something obvious, then my apologies.

Poincare commented 6 years ago

I believe the correct approach is the stop inflight queries at the application level and then call resetStore. The operation that resetStore represents is certainly not safe with inflight queries, so although I can imagine some cases where you might want to catch the error and proceed on with the reset anyway, it probably isn't a great practice.

I'm not sure if I completely grok the problem. I imagine that you do see the error "Store reset while query was in flight" in your application - is this error not possible to catch?

rares-lupascu commented 6 years ago

is this issue related to this one? https://github.com/apollographql/apollo-link-state/issues/198

kamranayub commented 6 years ago

In my case, I was running into this and needed a way to wait for in-flight queries to resolve. It worked pretty well in my case because I have wrappers around calling refetch for my observable queries, so all I really did was the following:

const inflightPromises = [];

function refetch(variables) {
  const refetchPromise = myObservable.refetch(variables).then(results => {

    // you could use native array methods here too
    // just remove the promise from the array
    _.remove(inflightPromises, p => p === refetchPromise);

    return results;
  });
  inflightPromises.push(refetchPromise);
  return refetchPromise;
}

function reset() {

  // Promise.all does not resolve with empty arrays
  const waitFor = inflightPromises.length 
    ? Promise.all(inflightPromises) 
    : Promise.resolve();

  // maybe also guard against calling this while waiting

  return waitFor.then(() => apolloClient.resetStore());
}
wmertens commented 6 years ago

@Poincare When using react-apollo, the state of inflight queries is an implementation detail. Should this problem be reported there?

I think the correct solution would be to wait for all pending queries to finalize, meanwhile holding any new incoming queries, resetting the store, and then running the pending queries. This is very hard to do at any level other than the client, no?

My use case is that when the user changes the language, I want to refetch everything. The "better/more efficient" alternative is passing the current language to every query, which is a lot of work for a pretty rare event.

There are a bunch more people with this issue at #1945…

wmertens commented 6 years ago

That said, it seems that in my case the issue is solved by resetting the store at the same time as setting the new language state instead of after the language state was set.

wmertens commented 6 years ago

@Poincare

The operation that resetStore represents is certainly not safe with inflight queries, so although I can imagine some cases where you might want to catch the error and proceed on with the reset anyway, it probably isn't a great practice.

I'm hitting the error again in another place, and I can't work around it. I'm having a hard time imagining a situation where you call client.resetStore() and then you don't want in-flight queries to be reset as well. Can you elaborate?

stevenkaspar commented 6 years ago

Not sure this helps anyone in this thread but I didn't think to see if resetStore was a Promise and turns out it is. For me it was an issue of the requests that were happening right after resetStore - not the already in flight ones (I think). So changing this

cookies.set('jwt', response_data.data.getCreds.token)
this.props.client.resetStore()
this.props.loggedIn(response_data)

to this

cookies.set('jwt', response_data.data.getCreds.token)
this.props.client.resetStore().then(() => {
   this.props.loggedIn(response_data)
}) 

got rid of the error

zvictor commented 6 years ago

@joenoon I created a reproducible to what could be the same issue, but it's not clear to me if it's actually the same issue or not. Could you take a look at #3555 and confirm if the issue is a duplicate?

dallonf commented 6 years ago

I agree with the sentiment that in-flight queries should be automatically dropped and refetched instead of throwing errors (or at least, that behavior should be available as an option).

Here's my situation: after completing a mutation with lots of side effects, I would like to drop the entire cache (in lieu of support for targeted cache invalidation) and then navigate to the next page.

If I call await client.resetStore(); history.push(nextPage);, the current page will visibly refetch all of its data, the app will wait for this to finish, and then navigate to the next page. This is not desirable because the app waits for data that isn't needed anymore to finish loading, and it creates visual noise as the previous page goes into its loading state, then shows its refetched data for a split second before navigating to the next page, which will also show a loading state before settling with the latest data.

If I instead reverse the process to history.push(nextPage); await client.resetStore();, I get the error shown here. I get the same result if I don't wait for the store to finish resetting before navigating (client.resetStore(); history.push(nextPage);).

larixer commented 6 years ago

@dallonf If you want to drop entire cache without triggering reload of current page queries you can achieve that by calling client.cache.reset() instead of client.resetStore(). E.g. call reset() on Apollo Cache directly somehow.

dallonf commented 6 years ago

@vlasenko Ah, thanks, that's definitely an easier workaround than what I was going for (forcing an empty render while resetting the store), although this still definitely needs to be addressed. Relying on an undocumented internal function shouldn't be the official solution to any problem!

larixer commented 6 years ago

@dallonf I'm not from official team, just a user. I understand that calling client.cache.reset() is not nice, thats why I added:

E.g. call reset() on Apollo Cache directly somehow.

If you have access to Apollo Cache somehow, you can call reset() directly on cache instance, this is an official way to reset it, I believe, without relying on internals.

mrdulin commented 6 years ago

same issue.

Here is my code snippet:

onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors)
          graphQLErrors.map(error => {
            // console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
            if (error.code === 1001) {

              auth.signout();
              //client.resetStore()
              client.cache.reset();
              window.location.replace('#/login');

              //client.resetStore()
              // .then(result => {
              //   window.location.replace('#/login');
              // })
              // .catch(err => {
              //   console.log('reset store error: ', err);
              // });
            }
          });
        if (networkError) console.log(`[Network error]: ${networkError}`);
      }),

if I ignore handling the promise client.resetStore() method returned. It will throw Store reset while query was in flight. error.

And, I have another issue. If graphQLErrors happened, client.resetStore().then().catch() will always trigger catch and dead loop.

hwillson commented 6 years ago

This issue is related to https://github.com/apollographql/apollo-client/issues/3555, https://github.com/apollographql/apollo-client/issues/3766 and https://github.com/apollographql/apollo-client/issues/3792. We'll address them all in one shot.

joelgetaction commented 6 years ago

@Poincare I don't understand your point "I believe the correct approach is the stop inflight queries at the application level and then call resetStore" - can you elaborate on how we could track all inflight queries at the application level so that we could stop them? The whole point of Query as I understand it is to make the use of GraphQL declarative, so our code should have no idea what queries are running at any one point and it would be extremely difficult to track all running queries ourselves since they get fired off (at GraphQL's discretion) based on when Query elements are mounted ... So how could a user track these queries? I can't see an easy or reliable way of doing that but can you suggest one? :-)

I think the most reliable and consistent approach is for the Apollo code to stop all queries when resetStore is invoked. Otherwise signout and signin of users is a lot trickier than it needs to be.

Thoughts? I'm open to all feedback and thanks for all of your hard work on GraphQL and Apollo. :-)

joelgetaction commented 6 years ago

OK, the way I solved this in the short term is to just totally blow away/delete/GC the apollo client when the user signs out and create a whole new apollo client when they sign in. That seems to have fixed things.

It also seems a reasonable thing to do since when the user signs out they are effectively saying "get rid of my data on this browser because I'm done using this app or another user wants to sign in" so getting rid of the whole client, while it feels clunky, does a reasonable job of deleting all the data. It is less desirable for the case where a JWT token has just expired and the user needs to re-login, but maybe we can find a better solution for that case?

Here's some sample code (not heavily tested, experimental), posting it in case it might help somebody else:

//
// App.js
//
import React, { Component } from "react";
import { ApolloProvider, Query } from "react-apollo";
import "./App.css";
import AuthStatus from "./components/AuthStatus";
import { init } from "./graphql/Client";

function setupApollo() {
  const authTokenKey = "authToken";
  const authTokenProvider = {
    get: () => localStorage.getItem(authTokenKey),
    set: authToken => localStorage.setItem(authTokenKey, authToken),
    remove: () => localStorage.removeItem(authTokenKey),
  };

  const apollo = init({
    uri: "http://localhost:4000/graphql",
    authTokenProvider,
  });

  return { authTokenProvider, apollo };
}

class App extends Component {
  state = { authTokenProvider: null, apollo: null };

  logout = () => {
    const { authTokenProvider } = this.state;
    if (!authTokenProvider) {
      return;
    }

    authTokenProvider.remove();

    const newApollo = setupApollo();
    this.setState({ ...newApollo });
  };

  login = () => {
    const { authTokenProvider } = this.state;
    if (!authTokenProvider) {
      return;
    }

    const authToken = "from your server after successful sign in ...";

    authTokenProvider.set(authToken);

    const newApollo = setupApollo();
    this.setState({ ...newApollo });
  };

  componentDidMount() {
    const newApollo = setupApollo();
    this.setState({ ...newApollo });
  }

  renderIsLoggedIn() {
    // There would be much more to render here normally, keeping this short as an example ...
    return <p>Logged in!</p>
  }

  renderIsNotLoggedIn() {
    // There would be much more to render here normally, keeping this short as an example ...
    return <p>Logged out!</p>
  }

  render() {
    const { apollo } = this.state;

    if (!apollo) {
      console.log("empty app render");
      return null;
    }

    return (
      <ApolloProvider client={apollo.client}>
        <AuthStatus>
          {({ status }) =>
            status === "loggedIn"
              ? this.renderIsLoggedIn()
              : this.renderIsNotLoggedIn()
          }
        </AuthStatus>
      </ApolloProvider>
    );
  }
}

export default App;

//
// AuthStatus.js
//
import PropTypes from "prop-types";
import React from "react";
import { Query } from "react-apollo";
import { getAuthStatus } from "../graphql/Queries";

function AuthStatus(props) {
  const { children } = props;
  return (
    <Query query={getAuthStatus}>
      {({ loading, error, data }) => {
        if (loading) return <p>Loading ...</p>;
        if (error) return <p>Error: {error.message}</p>;

        if (data && data.authStatus) {
          return children({ status: data.authStatus.status });
        } else {
          return null;
        }
      }}
    </Query>
  );
}
AuthStatus.propTypes = {
  children: PropTypes.func.isRequired,
};

export default AuthStatus;

//
// Queries.js
//
import gql from "graphql-tag";

export const getAuthStatus = gql`
  query AuthStatus {
    authStatus @client {
      id
      status
    }
  }
`;

//
// Client.js
//
import ApolloClient from "apollo-boost";

function isUnauthenticatedError(err) {
  if (!err) {
    return false;
  }

  if (/You must be signed in/i.test(err.message)) {
    return true;
  }

  if (err.extensions && err.extensions.code === "UNAUTHENTICATED") {
    return true;
  }

  return false;
}

export const init = ({ uri, authTokenProvider }) => {
  const authStatusData = () => ({
    authStatus: {
      id: "authStatusKey",
      __typename: "authStatus",
      status: authTokenProvider.get() ? "loggedIn" : "loggedOut",
    },
  });

  const client = new ApolloClient({
    uri,
    clientState: {
      resolvers: {
        Query: {
          authStatus: () => {
            return authStatusData().authStatus;
          },
        },
      },
    },
    request: operation => {
      const authToken = authTokenProvider.get();
      if (authToken) {
        operation.setContext({
          headers: {
            authorization: `Bearer ${authToken}`,
          },
        });
      }
    },
    onError: ({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        graphQLErrors.forEach(err => {
          if (isUnauthenticatedError(err)) {
            authTokenProvider.remove();
            const newData = { data: authStatusData() };
            client.cache.writeData(newData);
          }
        });
      }
    },
  });

  return { client };
};
larixer commented 6 years ago

@joelgetaction Why just not call reset() on the instance of Apollo Cache when you need to sign out the user and drop the cache? You are creating something like InMemoryCache is it right? Just call reset() on the instance of InMemoryCache. I think it is the simplest approach.

joelgetaction commented 6 years ago

@vlasenko thanks for the reply. Just resetting the cache didn't seem to work well. There were weird visual artifacts and I was still getting the error about inflight requests. I agree that your inflight suggestion would be simpler, but it didn't seem to work reliably ... And also, I need something to reset the authStatus and resetCache didn't handle that.

Is there a cost to blowing away and recreating the apollo client like I'm doing? I mean my React app seems very fast even when doing that - instrumenting, it doesn't seem to take longer than 1 ms so is there an advantage to resetCache other than it might be simpler?

Thanks again for your help!

larixer commented 6 years ago

@joelgetaction We are using cache.reset(): https://github.com/sysgears/apollo-universal-starter-kit/blob/2fb6562f02db4a68705fd047b2b85b56f00fe5b1/packages/client/src/modules/user/access/index.js#L11 It works reliably for us. We don't have error about inflight request. If you have this error it means you are still calling resetStore() somewhere. Just remove these calls. Use only cache.reset()

I'm not sure about the cost of recreating apollo client. I do not think there is some advantage in calling reset() on the cache directly over recreating apollo client, except the former is just simpler. And as I said we are using approach with cache.reset() for months already in production and we haven't seen reliability issues with it.

kamranayub commented 6 years ago

Another tip, as we use JWT and auth as well. You can add a hook for all network errors on the client. When you detect a 401, you can immediately handle the logout/login flow.

I didn't think if doing a cache.reset() we might add that too.

On Fri, Nov 2, 2018, 02:49 Victor Vlasenko notifications@github.com wrote:

@joelgetaction https://github.com/joelgetaction We are using cache.reset():

https://github.com/sysgears/apollo-universal-starter-kit/blob/2fb6562f02db4a68705fd047b2b85b56f00fe5b1/packages/client/src/modules/user/access/index.js#L11 It works reliably for us. We don't have error about inflight request. If you have this error it means you are still calling resetStore() somewhere. Just remove these calls. Use only cache.reset()

I'm not sure about the cost of recreating apollo client. I do not think there is some advantage in calling reset() on the cache directly over recreating apollo client, except the former is just simpler. And as I said we are using approach with cache.reset() for months already in production and we haven't seen reliability issues with it.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/apollographql/apollo-client/issues/2919#issuecomment-435298190, or mute the thread https://github.com/notifications/unsubscribe-auth/AAiaa2dOtmAy5zazSXw0iffdP8VXs0pxks5uq_kLgaJpZM4Rvful .

joshdcuneo commented 5 years ago

Not sure if this is relevant to all the use cases here but this flow is working ok for me:

login = async ({ token, user, authenticated }) => {
    const {
      props: { client }
    } = this;
    if (authenticated) {
      localStorage.setItem("token", token);
      await client.resetStore();
      this.setState({ user });
    }
    return null;
  };

  logout = async () => {
    const {
      props: { client }
    } = this;
    localStorage.setItem("token", "");
    this.setState({ user: null, token: null });
    await client.resetStore();
  };
lukejagodzinski commented 5 years ago

@joshdcuneo your solution works as you're storing "login" state in the React component state which is not a case in most examples presented here. The problem occurs when you try to store "login" state in the apollo cache which is the most obvious place to store it. I want to have access to it from any place in the app.

It's more and more frustrating to use Apollo Client. With Redux I didn't have such kind of problems. I'm really considering getting back to Redux for local state management. I will prepare reproduction repository tomorrow to show that this is still an issue

lukejagodzinski commented 5 years ago

I've created simplest possible reproduction repository here: https://github.com/lukejagodzinski/apollo-client-reset-store-bug

Error is thrown on each sign out (client.resetStore()). I'm not doing any fancy things here just one server query (when signed in) and one mutation of the client cache for storing sign in status. I think that here https://github.com/apollographql/apollo-client/blob/a444e58cea27e14ce4c4f2053b0322dcd532d0fa/packages/apollo-client/src/core/QueryManager.ts#L827 error shouldn't be thrown if promises were rejected because of the store being reset.

To my surprise it take really long time to resolve those promises. Even when result of the query was already displayed the promise related with this query is still being processed? Maybe it some other bug in the code.

I've also recorded short screencast showing a bug https://youtu.be/hY_JiLApXzk

WilliamConnatser commented 5 years ago

I was just having the same issue. After some experimentation, including some fixes listed above (to no avail,) I ended up making my logout function synchronous and it got rid of the error.

No Error:

            signoutUser: () => {
                //Remove token in localStorage
                localStorage.setItem("token", "");
                //End Apollo Client Session
                apolloClient.resetStore();
            }

Error:

            signoutUser: async () => {
                //Remove token in localStorage
                localStorage.setItem("token", "");
                //End Apollo Client Session
                await apolloClient.resetStore();
            }

I don't get why I was getting this error because I have the onError property for my ApolloClient instance set which seems to work in catching all other Apollo errors???


//Catches all errors
    onError: ({
        operation,
        graphQLErrors,
        networkError
    }) => {
        //operations object not currently in use, but stores query/mutation data the error occurred on
        if (networkError) Vue.toasted.show(networkError.message);
        if (graphQLErrors) {
            for (let err of graphQLErrors) {
                Vue.toasted.show(err.message);
            }
        }
    }```
eric-burel commented 5 years ago

Hi, not sure if it is worth opening a new issue for this, but sometimes resetStore() neither resolve or fails. This is very hard to reproduce, it only happens when I run my app in a popup window, and I log out. Then, when I sign in again, the promise eventually throws a "store reset when query in flight" error.

Any idea what could cause resetStore() to wait indefinitely ? Could it be related to some page reload, an invalid URL or a query that fails silently ? Edit: the reset store error also seems to happen for no reason, even when I don't call resetStore(). Could SSR provoke this error too ?

doomsower commented 5 years ago

@eric-burel I think I ran into something similar, but was too lazy to open issue or create minimal reproduction. Here is what happened for me:

eric-burel commented 5 years ago

@doomsower very interesting, I will dig that later but thank you very much for the insight. This might be related indeed, as the view changes on logout even if the resetStore() is not done. It also happens on login (without logout), which also triggers some redirect.

Edit: this has been solved in Vulcan by @ErikDakoda : https://github.com/VulcanJS/Vulcan/pull/2313. Instead of relying on a Promise, the solution for us was to use the new onResetStore callback of the Apollo client. This way, the callback is not registered in the logout React component, which may be erratically dismounted depended on your workflow (eg with an optimistic UI), but at the Apollo client level. So it is preserved and run even after an amount. This issue has disappeared so far and the code feels more robust.

jbaxleyiii commented 5 years ago

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!

wmertens commented 5 years ago

Still happening on stable as of two weeks ago

jedwards1211 commented 5 years ago

The way I see it, resetStore should:

And do the following in order:

Is there any case where this wouldn't be appropriate?

In-progress Query components don't need to know that the store is resetting; from their perspective it can just look like the query took from before the reset to after the reset to finish.

Queries that were finished before the reset, and subsequently need to be refetched, can just jump from ready state back to loading state -- or does this violate any critical assumptions?

Subscriptions don't need to see a status change due to the reset either. From their perspective it can look like they're still subscribed and there just haven't been any events.

jedwards1211 commented 5 years ago

@vlasenko resetting only the cache seems risky unless you're stopping all of your in-flight queries at application level. Queries that were in-flight before the reset (e.g. queries for the previous logged-in user) could write invalid results to the reset cache when finished.

MarcoScabbiolo commented 5 years ago

Then restart all pre-existing queries and subscriptions without causing them to error

@jedwards1211 a very common use case is to reset the store on logout, if the pre-reset queries are restarted, they will either fail or resolve with invalidated parameters (Authorization header of the not-logged-anymore user).

jedwards1211 commented 5 years ago

I don't know how you're putting in the auth header but in my case when the queries are restarted, the middleware to add headers gets called anew and injects the new user's auth header

khaphannm commented 4 years ago

Running into this issue now. Try all the solutions above, but seems not work. Anyone have some news about this issue ?

azuxx commented 4 years ago

Same here, and I am using @apollo/client 3.0.0-beta.43

anajavi commented 4 years ago

I can't reproduce this anymore with @apollo/client 3.0.0-beta.46.

hinok commented 4 years ago

It's happening in my project for modern apollo

├── @apollo/react-hoc@3.1.3
├── @apollo/react-hooks@3.1.3
├── @apollo/react-ssr@3.1.3
├── apollo-cache@1.3.4
├── apollo-cache-inmemory@1.6.5
├── apollo-client@2.6.8
├── apollo-link@1.2.13
├── apollo-link-batch-http@1.2.13
├── apollo-link-error@1.1.12
├── apollo-upload-client@12.1.0
├── apollo-utilities@1.3.3

Reported error

Error: Network error: Store reset while query was in flight (not completed in link chain)
    at new ApolloError (bundle.esm.js:63)
    at ObservableQuery.push.../../node_modules/apollo-client/bundle.esm.js.ObservableQuery.getCurrentResult (bundle.esm.js:159)
    at QueryData.push.../../node_modules/@apollo/react-hooks/lib/react-hooks.esm.js.QueryData.getQueryResult (react-hooks.esm.js:265)
    at QueryData._this.getExecuteResult (react-hooks.esm.js:73)
    at QueryData.push.../../node_modules/@apollo/react-hooks/lib/react-hooks.esm.js.QueryData.execute (react-hooks.esm.js:106)
    at react-hooks.esm.js:380
    at useDeepMemo (react-hooks.esm.js:354)
    at useBaseQuery (react-hooks.esm.js:380)
    at useQuery (react-hooks.esm.js:397)
    at Query (react-components.esm.js:8) "
    at Meta (http://localhost:8080/common.chunk.js:104011:3) // <------------ This is my component
    at Query (http://localhost:8080/app.chunk.js:167:26)

So basically what I'm trying to achieve is to reset apollo in-memory cache on redux store change when user logged out, it may be because user clicked "Logout" link intentionally or token expired etc.

After "logout" user is automatically redirected to another page that run some graphql queries about the page (head meta data like title, description etc.).

I'm not sure how should I call client.clearStore() from store.subscribe or redux middleware in a way that it won't conflict with any new or pending queries. Is there a way to safely schedule clear of apollo's cache?

Is something like below (https://github.com/apollographql/apollo-client/issues/3766#issuecomment-619829099) the way to fix it?

client.stop()
client.clearStore()
Fi1osof commented 4 years ago

I can't reproduce this anymore with @apollo/client 3.0.0-beta.46.

Just open react-development-tools, find ApolloProvider and exec

for (let i = 0; i< 3; i++) {
    console.log('i', i);
    setTimeout($r.props.client.resetStore, 10)
}

I still got error

Unhandled Runtime Error
Invariant Violation: Store reset while query was in flight (not completed in link chain)

Before, in pure js i just checking by

if(!client.queryManager.fetchCancelFns.size) {
   await client.resetStore()
}

but i white in typescript now and client.queryManager is private https://github.com/apollographql/apollo-client/blob/main/src/core/ApolloClient.ts#L74

It's so sad! I can't check this any more and apollo does not provide any checking(((

UPD. But i can check like this

client["queryManager"].fetchCancelFns.size

and no typescript errors...

khushahal-sharma commented 3 years ago

Calling client.stop() method before clearStore solved my problem.

client.stop() client.clearStore();

Accroding the official doc: Call this method to terminate any active client processes, making it safe to dispose of this ApolloClient instance. https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.stop

Fi1osof commented 3 years ago

Calling client.stop() method before clearStore solved my problem.

client.stop() client.clearStore();

Accroding the official doc: Call this method to terminate any active client processes, making it safe to dispose of this ApolloClient instance. https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.stop

@khushahal-sharma it's bot optimal. For example, i have many subscriptions and got many events for updating at the moment. In this case starts many cicles "Send query, stop, send another...". Checking global sending status more useful.

terryatgithub commented 1 year ago

Calling client.stop() method before clearStore solved my problem.

client.stop() client.clearStore();

Accroding the official doc: Call this method to terminate any active client processes, making it safe to dispose of this ApolloClient instance. https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.stop

still encounter this error with "@apollo/client": "~3.4.17". so we will adopt this solution for now.