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.58k 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.

nawfalhaddi 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 🤷

Hello, I'm running 4 API calls in the same component with different keys using "useQuery" and I used QueriesObserver to see what is happening in the background. Basically, in the observer, the data is up to date and the isLoading state has changed to false, but this does not reflect on the variables which are extracted from useQuery. is there a way to force a useQuery to check the observer again?

@TkDodo Btw this is on a react native project

TkDodo commented 2 years ago

@nawfalhaddi if its related to 4 useQuery queries running in parallel, can you reproduce this in a sandbox?

Btw this is on a react native project

Are all problems posted in this issue related to react native?

nawfalhaddi commented 2 years ago

@TkDodo I tried to reproduce the issue on sandbox but it's not happening, maybe it's related to react native specifically. I will create an example repo of react native project and try to reproduce the problem on it, I will share it with you soon.

FeliceGeracitano commented 2 years ago

could this be related to https://github.com/tannerlinsley/react-query/issues/3332 ?

vdutz commented 2 years ago

@TkDodo I am experiencing the same issue on version 3.24.4 - isLoading gets stuck on true. I'm using React Native and this happens to me randomly (but only on app startup). It seems to happen about every 1 in 10 or 20 times, so is quite difficult to replicate.

Any update on this?

I also have the same question as @nawfalhaddi:

is there a way to force a useQuery to check the observer again?

gus-delpino commented 2 years ago

Hey y'allz. I've been dealing with this for 8 hours myself and this is my story:

My query that's stuck in loading has an enabled property, and I had a race condition where:

  1. Component would get enabled set to true, so query fires
  2. I have an axios interceptor that calls a function that updates the enabled property above
  3. Before the API call finishes its normal lifecycle (succeed or fail), enabled is set to false before isLoading is flipped back to false. Basically the query gets disabled in a "loading" state.

For me I just needed to call resetQueries, but the race condition made the problem intermittent and it would only happen on app startup. Hopefully it helps somebody

SevenOutman commented 2 years ago

I'm experiencing the same issue on version 3.38.0. The useQuery call returned { data: undefined, error: null, isLoading: true } once and never got updated. It seems to only happen when my Jest tests are run in CI environment with multiple workers (with --maxWorkers=4 flag). It doesn't happen on my local machine, nor when I set --maxWorkers=1 (single worker mode). DK if the info helps.

Update

It's then fixed by adding --silent flag to Jest. Maybe it's noting to do with react-query itself, but the logs slowing down the thread and finally causing the waitFor util to timeout.

TkDodo commented 2 years ago

imo it doesn't make much sense if more people keep commenting on this closed issue from over a year ago. If you have a consistent reproduction, or any reproduction for that matter, please open a new issue. There isn't much I can do otherwise I'm afraid.

balteo commented 2 years ago

Hi @TkDodo

I have implemented a small application that reproduces the issue described in this discussion. Here is the repo: https://github.com/balteo/react-query-issue-reproduction

Just git clone the repos and yarn install and yarn run test

Please let me know if I can help further.

Kind regards

TkDodo commented 2 years ago

@balteo from what I can see, you are not "waiting" until the request is complete - you're just rendering the form, and then expect the options to instantly be in there. But that's not how it works. you need to wait until the request is completed. Adding something like:

expect(await screen.findByText(/accommodation/i)).toBeInTheDocument()

before actually trying to select it should work. However, I am also seeing this in the logs:

Error: Error: connect ECONNREFUSED 127.0.0.1:80

I don't see anything that actually sets up msw. Have you read my blog post about that topic?

Once I add msw setup like described here:

things start to work, logging is:

  console.log
    categories:  []

      at MyForm (src/components/form/form.tsx:15:13)

  console.log
    isLoading:  true

      at MyForm (src/components/form/form.tsx:16:13)

  console.log
    categories:  [
      { id: 1, name: 'Personals' },
      { id: 2, name: 'Accommodation' },
      { id: 3, name: 'Employment' }
    ]

      at MyForm (src/components/form/form.tsx:15:13)

  console.log
    isLoading:  false

so nothing is "stuck in loading" anymore.

The next error is then:

Cannot select multiple options on a non-multiple select

So yeah, that's something in the test / component itself, but reducing it to a single select makes the test pass. Here is the final code that worked for me in form.test.tsx:

import { setupMyForm } from '../../../test/setup-form';
import {server} from '../../../test/server'
import {screen} from '@testing-library/react'

describe('tests', () => {
    beforeAll(() => server.listen())
    afterEach(() => server.resetHandlers())
    afterAll(() => server.close())

    test('should submit form', async () => {

        const {
            renderForm,
            changeCategorySelect,
        } = await setupMyForm();

        renderForm();
        expect(await screen.findByText(/accommodation/i)).toBeInTheDocument()
        await changeCategorySelect(['Accommodation']);
    });
})
balteo commented 2 years ago

@TkDodo Thanks a lot for your detailed reply!

Stryzhevskyi commented 1 year ago

Hi I have a similar issue on the version 4. I see there is a tag port-to-v4 in the #1737 fix, but was this fix really ported to v4? I'm not familiar with the organisation of the repo. There are 4 branches: 1.x, 2.x, v3, v5 and none for v4.

@TkDodo, I'm sorry for disturbing you, but could you check if this fix is present in v4, please?

I've spent many hours trying to track the root cause o the issue, because it happens only on CI (like 1/5 times) on my project and then found this issue for v3.

TkDodo commented 1 year ago

I renamed the port-to-v3 label as port-to-v4, that was probably not a good idea, sorry.

code is quite different now, I would need to see a codesandbox reproduction that fails on v4 please.

the code path you're showing has been removed in a later v3 version to ensure concurrent rendering capabilities:

annjawn commented 1 year ago

I am going to add a +1 to this. I am facing this exact issue where isLoading gets stuck in true and data comes back as undefined. I am using the same query in two components the first component fires the query and gets the data fine, but on the second component the same query (with the same key) gets stuck and returns undefined. This is a bizarre situation that I am not sure I am able to wrap my head around.

Here's whats happening

Component - 1
    Query 1 --> Works 
    Query 2 --> Works

Component - 2
   Query 1 --> Works with same key as in component 1
   Query 2 --> Doesn't Work with same or different key

Component 1 and 2 are quite similar in how they work, Query-1 and Query-2 uses the same async functions (with different service endpoints). I will say that at some point I had this QueryClient setup (I am not sure why I setup this way)

const queryClient = new QueryClient({defaultOptions: {
  queries: {
    retryDelay: 1,
    retry:0,
  }
}});

Changing this to the following didn't work either

const queryClient = new QueryClient();
annjawn commented 1 year ago

ok sheesh i think i found the issue. With Multiple queries comes multiple isLoading the part of the component that depended on the second query was rendering too soon causing the issue. So these multiple isLoadings and checking of data for each of these queries will need to be handled individually. Any early rendering causes the query status to stay stuck (or rather give the illusion i should say).

filip-kis-fsc commented 1 year ago

I am experiencing this issue consistently in a larger project with

    "@tanstack/react-query": "4.26.0",
    "react": "18.2.0",
    "next": "13.2.3",

but only if react is running in strict mode.

Unfortunately I do not have a minimal repro example.

hectopod commented 1 year ago

I am facing the same issue only in Cypress. (Edit: working in Playwright, switching to it)

Devtools indicate that the data is fresh but the loader is still loading (isFetching).

I am using react query for server state and redux for client state. Could be related

agos commented 1 year ago

I'm also facing the same issue since recently on v4.

invalidating a query (as result of a mutation, or manually via dev tools) will make it transition to 'isFetching', but won't transition again to 'success'. DevTools show it stuck in fetching. Components using the query will render only once and see it in 'success' status with old data, never receiving the updated data.

Unfortunately no minimal repro from me either, but I have noticed that this happened once I moved some of the query dependencies out of a context and inside a state manager (Jotai). In theory this does not imply any change in the query rendering, but the timing is very suspicious in my case

crutchcorn commented 10 months ago

One thing to remember if you're in the MSW/Testing land - set retry to 0 during testing.

Remember that Query intentionally backs off over a period of time to retry searching, upwards of a couple of seconds. This is a good thing, as it leads to better UX.

As a result, my tests were failing until I did this.

vaibhavshn commented 8 months ago

We also faced the same issue where for some reason some users were not able to fetch the data properly.

On further debugging found the fetchStatus was always paused. After going through docs, we passed the networkMode: always and it worked fine this time. We also verified this by removing the networkMode config.

So this probably means something wrong is happening in the code when the default networkMode: on value is set.

cc @TkDodo

NOTE: Using the latest version current: 4.35.3

TkDodo commented 8 months ago

So this probably means something wrong is happening in the code

no, this is an issue with navigator.onLine in chrome on macos, which we are using in v4 to determine if you are online or not.

Since navigator.onLine is broken, we've moved away from using it in v5

vaibhavshn commented 8 months ago

Since navigator.onLine is broken, we've moved away from using it in v5

Thanks for the quick reply, will upgrade to v5.

Dougmarcolino commented 8 months ago

I just needed to update to the new version of Chrome, now it's working.

image

macOS: Ventura 13.5.2 (Apple M1 Pro)

Harvel-Kay commented 8 months ago

See am encountering the same issue with v4.36, It tends to make requests successfully to jsonplaceholder however when I make a request to my local server it then stucks on the loading, When I use the use Effect hook and state everything works well, I've seen this stuck issue has been perturbing from v2 all the way to v4, why on earth haven't you figured out why your library behaves like this, it's pretty frustrating and I'll chose to revert to the older way of fetching data 😡😡😡😡😠😠

hoanmq commented 8 months ago

We have a large number of users using the app, occasionally a few users encounter this issue where all the functions using react query get stuck at the loading stage, whereas direct calls using axios to the API work fine. The characteristic is that the loading is shown, but no requests seem to be made to the server. When we instruct the users to completely close the browser and reopen it, everything starts working normally again. It's quite strange.

  "@tanstack/react-query": "^4.24.4"
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
    },
  },
});
TkDodo commented 8 months ago

It's quite strange.

again, I'm positive that this is related to navigator.onLine being broken in chrome, which is not our fault. What is our fault is that we were relying on it to work properly in the first place, which is what we've changed in v5.

you can validate that assumption by looking at the fetchStatus returned from useQuery when that happens. If the fetchStatus is 'paused', you'll know that this is indeed the case.

again, you can:

hoanmq commented 8 months ago

It's quite strange.

again, I'm positive that this is related to navigator.onLine being broken in chrome, which is not our fault. What is our fault is that we were relying on it to work properly in the first place, which is what we've changed in v5.

you can validate that assumption by looking at the fetchStatus returned from useQuery when that happens. If the fetchStatus is 'paused', you'll know that this is indeed the case.

again, you can:

  • update to v5, which has a different default behaviour
  • set networkMode: 'offlineFirst' to work around it
  • get the bugs fixed in chrome 🤷

Thank you for responding, however, it happens randomly across different browsers including Edge, Brave, etc. I will try upgrading to v5.

TkDodo commented 8 months ago

edge and brave are both built on chromium ... they are the same thing and have the same behaviour / bugs in this regard

117 commented 6 months ago

Firefox 120.0.1 (64-bit) still occurring M2 Mac, Sonoma 14.1.1

TkDodo commented 6 months ago

@117 on query v4 or v5? We haven't done anything on v4 - and also won't because the fix / workaround was a change in behaviour, so it's a breaking change

lundstromdavid commented 5 months ago

I've had the same problem for a while and haven't bothered to investigate until now. I've now figured out the reason which for me was that I called queryClient.reset() on mount, as a side effect of resetting the client when the user signs out.

Here's a sandbox: https://codesandbox.io/p/sandbox/react-query-bug-sfm3fx?file=%2Fsrc%2FApp.tsx%3A16%2C2

I've also included the fix that worked for me, which you can toggle on/off using the useFix flag in App.tsx.

Is this a bug in React Query or just bad implementation on my part? @TkDodo

TkDodo commented 5 months ago

@lundstromdavid it seems like you're clearing the cache while the query is in-flight, I don't know why you would do something like that. clear() is not the same as resetQueries() because clear does not inform observers about the change

lundstromdavid commented 5 months ago

@TkDodo Thanks for the reply! Should have read up more before. Although it does seem quite unintuitive for me that clear() can stall in-flight queries in a loading state, but what do I know :man_shrugging:

shuweili1122 commented 4 months ago

can confirm this indeed happens with react: 18.2, and @tanstack/react-query 4.36.1 on post request when running jest test with axios 1.6.2 and axios-mock-adapter 1.22.0, still happens after upgrading @tanstack/react-query to 5.17.19, I can see the result in useQuery, but it never returns to the component, and the status is always loading; hence replaced @tanstack/react-query with vanilla axios for my data fetching part, and the failed test passes...

Also found a very hacky workaround if you don't want to replace @tanstack/react-query with other libraries: basically just do a data fetching with axios-mock-adapter in beforeAll or beforeEach, then run your regular test, and in your test block, do a data fetching with axios-mock-adapter again... your test will then have about 1/2 chance of passing... super confusing bug

TkDodo commented 4 months ago

One thing you can try on v5 is to disable our batching and see if that helps:

import { notifyManager } from '@tanstack/react-query'

// invoke callback instantly
notifyManager.setScheduler((cb) => cb())

see: https://tanstack.com/query/latest/docs/reference/notifyManager#notifymanagersetscheduler

Codyooo commented 1 month ago

I would like to recommend changing the default setting of the networkMode configuration in React Query to 'always'. Given the library's widespread use, with over 2 million downloads per week, the current default setting may lead to significant issues.

Recently, we encountered a bug where the system did not make any network requests due to the networkMode being set to 'online'. This issue arose because the browser's navigator.onLine API inaccurately reported the network status as offline, even though the connection was active. The impact of this bug is particularly severe as it can cause key pages to remain stuck on a loading screen without any indication of the problem. Considering that React Query is a critical part of many projects, such errors can have drastic consequences, including potential job losses, especially if key stakeholders encounter these issues firsthand.

It can be challenging to identify this issue without delving into the source code, making it a silent but deadly problem. To enhance reliability and prevent such problematic scenarios, I strongly urge you to consider setting the default networkMode to 'always'. This change would ensure that network requests are made regardless of the navigator.onLine status, thus providing a more consistent and dependable user experience.

Thank you for considering this suggestion. I believe it will significantly improve the robustness of React Query and safeguard many developers and businesses from facing similar issues.

manvydas-tbol commented 1 week ago

One thing you can try on v5 is to disable our batching and see if that helps:

import { notifyManager } from '@tanstack/react-query'

// invoke callback instantly
notifyManager.setScheduler((cb) => cb())

see: https://tanstack.com/query/latest/docs/reference/notifyManager#notifymanagersetscheduler

Hey @TkDodo I was facing identical issue. This helped when running cypress tests. Is there any other way to achieve this without changing global configuration?

TkDodo commented 1 week ago

you can set the scheduler inside cypress tests only.

manvydas-tbol commented 1 week ago

Hey @TkDodo Thank for quick reply.

However, adding it like this

 it('Cypress test demo', () => {
    notifyManager.setScheduler((cb) => cb());

    // Test logic
 });

or this

 notifyManager.setScheduler((cb) => cb());
 ...

     it('Cypress test demo', () => {

        // Test logic
     });

Doesn't work. Only works if directly added to the component