TanStack / query

🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
https://tanstack.com/query
MIT License
40.54k stars 2.73k forks source link

useQuery randomly not returning data and stuck in loading #1657

Closed vsaarinen closed 3 years ago

vsaarinen commented 3 years ago

Describe the bug After upgrading to react-query 3.5.11 from 2.26.4 last week, our project's Cypress tests started failing randomly because the app started showing a loading spinner at random times. After a lot of head-banging, we finally tracked down the issue: even though, based on the network inspector, the request had gone through successfully, react-query's useQuery was returning an object with isLoading as true and data as undefined. Reverting back to react-query 2 fixed the problems.

Most of the requests in our Cypress tests are stubbed using cy.server and cy.route. This probably shouldn't affect react-query but mentioning it here just in case.

Our cache is configured in the following manner at the top of one of our source files, so this shouldn't be caused by us accidentally creating multiple caches:

const queryCache = new QueryCache({
  defaultConfig: {
    queries: {
      staleTime: 10 * 60 * 1000,
      cacheTime: 15 * 60 * 1000,
      retry: (
        _failureCount,
        error: any,
      ) => error.status !== 403 && error.status !== 401,
    },
  },
});

// Further down:

class AppProvider extends Component<{}, State> {
  public render() {
    return (
      <ReactQueryCacheProvider queryCache={queryCache}>
        {/* ... */}
      </ReactQueryCacheProvider>
    );
  }
}

To Reproduce Unfortunately, this happens randomly and is thus very hard to reproduce. The randomness does point towards some type of race condition. Our app performs many (~10) requests on startup.

Even though which tests fail is random, I can consistently get at least one test to fail. Is there something I can do to help track down what could be the issue?

Expected behavior useQuery should return the data it receives in the response once the request is successful.

Desktop (please complete the following information):

Additional context Happens on both Cypress version 5.3.0 and 6.2.1.

TkDodo commented 3 years ago

this looks surprisingly similar to https://github.com/tannerlinsley/react-query/issues/1653

boschni commented 3 years ago

Don't know how the tests are written but it might be good to check if they do not implicitly depend on certain timings. Are you able to reproduce it manually? This might be useful: https://docs.cypress.io/guides/core-concepts/retry-ability.html

vsaarinen commented 3 years ago

Don't know how the tests are written but it might be good to check if they do not implicitly depend on certain timings. Are you able to reproduce it manually? This might be useful: https://docs.cypress.io/guides/core-concepts/retry-ability.html

I haven't noticed this problem when using the app manually, so there's always a chance that this is somehow caused by Cypress. But a few reasons why I think this may be an issue with react-query:

I'll see if I can narrow down the react-query version where this started happening.

antonversal commented 3 years ago

I can confirm the same behavior after migrating from 2.23.1 to 3.5.16. isLoading is randomly returning true for queries with staleTime option. Everything works without staleTime.

boschni commented 3 years ago

Would be great if you guys could to provide a sandbox or something similar which we can use to debug the issue.

antonversal commented 3 years ago

Would be great if you guys could to provide a sandbox or something similar which we can use to debug the issue.

I've tried to reproduce in a sandbox without any luck (

josewhitetower commented 3 years ago

Commenting just to subscribe to the thread because I'm having the same issue and can't reproduce it on a sandbox. Happens on v2.26.3 and after updating to v3.6.0.

TkDodo commented 3 years ago

Can we try to narrow it down collectively? So far, we've seen that:

could you further narrow it down to a specific version between those two?

staleTime seems to be involved, but how do you set staleTime? via the default-config on the global cache like @vsaarinen has shown in the opening post (similar to what @fTrestour has posted in #1653), or are you setting the staleTime on the useQuery call directly?

TkDodo commented 3 years ago

I'm also seeing that both the opening post in this issue as well as in #1653 have the retry option set - could we rule that one out as well?

antonversal commented 3 years ago

@TkDodo I set staleTime in useQuery. No global settings and retry.

vsaarinen commented 3 years ago

Can we try to narrow it down collectively? So far, we've seen that:

  • it apparently still works on 2.23.1 according to @antonversal
  • it's no longer working on 2.26.3 according to @josewhitetower

could you further narrow it down to a specific version between those two?

For me, things work fine on 2.26.4. I'll try to find which version of 3 causes things to start breaking again.

vsaarinen commented 3 years ago

Started seeing the same issues after upgrading to 3.2.0 from 2.26.4. So it appears to be a regression introduced in version 3.

adeelibr commented 3 years ago

Started seeing the same issues after upgrading to 3.2.0 from 2.26.4. So it appears to be a regression introduced in version 3.

I have the same issue

fermmm commented 3 years ago

I finally found the cause at least for my specific case.

React-query performs a deep compare to update the component, so if your request returns the same data than in the previous request, react query will not update your component. This was happening randomly to me because the response of my request contains random changes but sometimes returns the same data. In my case I expect a re-render always. The solution in case you have the same problem is to disable deep compare by setting structuralSharing to false like this:

export const queryClient = new QueryClient({
   defaultOptions: {
      queries: {
         staleTime: Infinity,
         structuralSharing: false
      }
   }
});

This is a bug or at least confusing because it's an "anti-pattern" since react does not expose anything that works with deep compare like this. React is based on reference equality only.

This feature was introduced recently: https://github.com/tannerlinsley/react-query/issues/430

vsaarinen commented 3 years ago

@fermmm It sounds like your issue may be different than what I'm experiencing. My issue is that even though data was successfully received by the app, useQuery thinks that it's still loading the data and never provides it to the app.

fermmm commented 3 years ago

@fermmm It sounds like your issue may be different than what I'm experiencing. My issue is that even though data was successfully received by the app, useQuery thinks that it's still loading the data and never provides it to the app.

Yes my problem looks similar but may be not the same.

fermmm commented 3 years ago

Ok I had this issue in another request of my app and this time was not related with structuralSharing, I noticed {data: undefined, isLoading: true} randomly without getting the data. The only different thing with this useQuery() it was that I had many components mounted using the same useQuery() key, maybe is something that happens when one component is waiting for the request and other mounts with the same... I don't know.

vasconita commented 3 years ago

Same problem here. In my case I use staleTime: Infinity.

TkDodo commented 3 years ago

I would really like to get to the bottom of this issue, but if nobody can reproduce it in a codesandbox, it is almost impossible to narrow down.

vasconita commented 3 years ago

I forgot to add more info: I'm mounting a lot of components (50) that use this useQuery with Infinity staleTime and I don't have configured retry neither. I'm going to try to reproduce it in a sandbox.

vasconita commented 3 years ago

I have created a sandbox with the conditions that seem to be common to all of us (Infinity staleTime and having many instances) but cannot reproduce the error, I think it has only happened to me once. On my SPA it happens to me much more easily 🤔

TkDodo commented 3 years ago

From what I can see in the sandbox: are you also putting the whole result from fetch into the query cache in your app? This includes some non-serializable data, like the body, which is a stream, and lots of functions that return Promises, like .json()...

vasconita commented 3 years ago

No no, it was like this because since I don't use data in the example I didn't even worry with the response. I updated it and now I just return the JSON body (like in my app of course)

fermmm commented 3 years ago

In my case this only happens when I restart my RN app when all the components are loading for the first time

vsaarinen commented 3 years ago

I believe my issue has also appeared only on app start, in the initial loading of components.

TkDodo commented 3 years ago

I believe my issue has also appeared only on app start, in the initial loading of components.

I guess this is the only time when you can be "stuck in loading", because you are never in loading state again once you've successfully loaded data.

Question: In case this being-stuck happens, what do the react-query devtools say about the state of the query?

vsaarinen commented 3 years ago

Question: In case this being-stuck happens, what do the react-query devtools say about the state of the query?

The problematic query is the second from the bottom in the list, with 5 listeners and marked as stale:

Screenshot 2021-02-02 at 8 54 37 Screenshot 2021-02-02 at 8 54 47

I confirmed that the data for that query was received successfully when looking at the network developer tab. The weird thing is that the data is shown correctly in the devtools, but in our app the data property of the objected returned by that useQuery is undefined. Is there something more specific I should share from the devtools?

TkDodo commented 3 years ago

The weird thing is that the data is shown correctly in the devtools, but in our app the data property of the objected returned by that useQuery is undefined.

Yes, that does indeed seem weird. Are you using different staleTimes on the 5 observers? Everyone is mentioning staleTime: Infinity, but in your code, I am seeing a different staleTime - and your query is also marked as stale, which shouldn't be the case with staleTime: Infinity...

vsaarinen commented 3 years ago

Yes, that does indeed seem weird. Are you using different staleTimes on the 5 observers? Everyone is mentioning staleTime: Infinity, but in your code, I am seeing a different staleTime - and your query is also marked as stale, which shouldn't be the case with staleTime: Infinity...

I've never used staleTime: Infinity. Except for a couple of queries where these are overridden, I only use the time values from the defaultConfig as shown in the original post:

      staleTime: 10 * 60 * 1000,
      cacheTime: 15 * 60 * 1000,

Could the issue be somehow related to the fact that the devtools marks the query as stale even though it shouldn't be? Just guessing here since I don't know what "stale" means...

TkDodo commented 3 years ago

I've never used staleTime: Infinity.

thanks, I mixed that up a bit 😅 .

since I don't know what "stale" means.

a stale query is a query that will refetched in the background if a new subscriber mounts / the window is refocussed etc.

the devtools marks the query as stale

judging from the code, the devtools don't manipulate the queries (unless you press one of the buttons). They just display the current state of the cache.


So if your screenshot is from after the 5 subscribers mount, and you have a staleTime of 10 minutes - why are the queries showing up as stale ? They should show up as fresh (green) - just like the other queries 🤔

vsaarinen commented 3 years ago

So if your screenshot is from after the 5 subscribers mount, and you have a staleTime of 10 minutes - why are the queries showing up as stale ? They should show up as fresh (green) - just like the other queries 🤔

Good question. The top 6 queries in the list should be marked as fresh. Maybe this is related to the underlying cause?

TkDodo commented 3 years ago

Good question. The top 6 queries in the list should be marked as fresh. Maybe this is related to the underlying cause?

yes, maybe. Are you sure that all subscribers (all instances of useQuery) use the same config? If you have 4 subscribers with staleTime: 10 minutes, and one additional subscriber where the staleTime is 0, the smaller one will "win". Not sure if that could cause this problem ...

vsaarinen commented 3 years ago

Are you sure that all subscribers (all instances of useQuery) use the same config? If you have 4 subscribers with staleTime: 10 minutes, and one additional subscriber where the staleTime is 0, the smaller one will "win". Not sure if that could cause this problem ...

All of the subscribers listed in the screenshots use the default cacheTime of 15 minutes and staleTime of 10 minutes.

boschni commented 3 years ago

Seems like the cache is fine, but maybe one of the observers did not get updated somehow. Can you see in the devtools if one of the observers for that query has an incorrect currentResult?

vsaarinen commented 3 years ago

Seems like the cache is fine, but maybe one of the observers did not get updated somehow. Can you see in the devtools if one of the observers for that query has an incorrect currentResult?

Yep, there's an issue with the observers: the first three have the correct data in currentResult but the last one has invalid data, as shown in the screenshots below.

Screenshot 2021-02-02 at 12 01 41 Screenshot 2021-02-02 at 12 01 54
boschni commented 3 years ago

Just guessing here, but it seems technically possible that a query gets updated between a component mount and its useEffect.

  1. Component mount
  2. Query update
  3. Component useEffect (starts listening for updates)

In such case the component would miss the update.

Could you check if this change fixes it?

--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -528,6 +528,11 @@ export class QueryObserver<
       )

     if (query === prevQuery) {
+      if (query.state !== this.currentResultState) {
+        const prevResult = this.currentResult
+        this.updateResult()
+        if (prevResult !== this.currentResult) {
+          this.notify({ listeners: true })
+        }
+      }
       return
     }
vsaarinen commented 3 years ago

Could you check if this change fixes it?

After a few quick tests, it does seem like this fixes the issue. 🎉 But need to do a bit more testing...

I added the following console logging to make sure that the condition gets hit, and the weird thing is that the this.notify() never seems to be get called but the issue still seems to be fixed. Might the this.updateResult() call be enough to fix this?

    if (query === prevQuery) {
      if (query.state !== this.currentResultState) {
        console.warn('!!! query.state !== this.currentResultState !!!')
        const prevResult = this.currentResult
        this.updateResult()
        if (prevResult !== this.currentResult) {
          console.warn('!!! prevResult !== this.currentResult !!!')
          this.notify({ listeners: true })
        }
      }
      return
    }

Note that I had to upgrade my local react-query's copy of the react and react-dom devDependencies in order to be able to yarn link it to my project. This probably doesn't have an effect on this issue, however.

windward-hive commented 3 years ago

I can confirm the same behavior after migrating from 2.23.1 to 3.5.16. isLoading is randomly returning true for queries with staleTime option. Everything works without staleTime.

I'm currently experiencing this same issue. I started a discussion on this yesterday. Here

boschni commented 3 years ago

Could you check if the issue is fixed in v3.6.1?

vsaarinen commented 3 years ago

Haven't run into failing tests after upgrading to 3.6.1. Let's keep our fingers crossed.

Thanks a lot for the fix!

vasconita commented 3 years ago

It seems to solve the problem in my case. Thank you very much!!

antonversal commented 3 years ago

Thanks, @boschni. I do not see failing tests anymore as well.

ivillarreal91 commented 3 years ago

Hello everyone!!!

Has anyone experienced the same behavior but now with useQueries hook? it doesn't return data and gets stuck in loading randomly in the tests. I appreciate any support

"react": "^16.13.1"
"react-query": "^3.13.4"
cameronevans commented 2 years ago

@ivillarreal91 I'm seeing this as well with latest, were you able to find a solution? Using useQuery in one component and useQueries with the same key in another component causes the useQuery to get stuck. Going to try to reproduce in a CSB.

Toyurc commented 2 years ago

@ivillarreal91 I'm seeing this as well with latest, were you able to find a solution? Using useQuery in one component and useQueries with the same key in another component causes the useQuery to get stuck. Going to try to reproduce in a CSB.

I think this is the closest thing that describes the problem, I am currently having a similar issue and I just upgraded to the latest release. if you have keys like baa and baaFoo, the whole pages hangs and is stuck on isFetching

TkDodo commented 2 years ago

@Toyurc if you have a codesandbox reproduction, please open a new issue with it.

MatkoMilic commented 2 years ago

I have the same issue, its stuck at loading, while the exact same code works on another app.

TkDodo commented 2 years ago

@MatkoMilic again, this is not very helpful I'm afraid. Without an isolated reproduction, there is only so much I can do 🤷

ZiedHf commented 2 years ago

@TkDodo I am also facing the same issue and stuggling to reproduce it.

nawfalhaddi commented 2 years ago

I also have the same issue in my react native application, it is a random issue, sometimes isLoading and isFetching get stuck and data stays undefined even if the network request is done successfully. When I restart the app it works fine! Hopefully they can fix this issue as soon as possible