Skip to content

Commit 31bcaf1

Browse files
authored
fix(useIsFetching): account for fetches happening in the same render cycle (#3438)
if a fetch happens in the same render cycle as useIsFetching is mounting, but it is mounted after the query, what happens is that: - the initial state of `isFetching` is `0` because the effect to fetch the query hasn't started yet - the query effect runs before the isFetching effect, which means we miss the event, thus the next event we get from the subscription gives us 0 again so we need to check for isFetching in the effect that runs to set up the subscription as well. This catches fetches that basically started in the same effect cycle, but before "our" effect ran
1 parent ce61a2a commit 31bcaf1

File tree

2 files changed

+52
-5
lines changed

2 files changed

+52
-5
lines changed

src/react/tests/useIsFetching.test.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('useIsFetching', () => {
100100
}
101101

102102
renderWithClient(queryClient, <Page />)
103-
await waitFor(() => expect(isFetchings).toEqual([0, 0, 1, 2, 1, 0]))
103+
await waitFor(() => expect(isFetchings).toEqual([0, 1, 1, 2, 1, 0]))
104104
expect(consoleMock).not.toHaveBeenCalled()
105105
expect(consoleMock.mock.calls[0]?.[0] ?? '').not.toMatch('setState')
106106

@@ -159,4 +159,29 @@ describe('useIsFetching', () => {
159159
await sleep(100)
160160
expect(isFetchings).toEqual([0, 0, 1, 0])
161161
})
162+
163+
it('should show the correct fetching state when mounted after a query', async () => {
164+
const queryClient = new QueryClient()
165+
const key = queryKey()
166+
167+
function Page() {
168+
useQuery(key, async () => {
169+
await sleep(10)
170+
return 'test'
171+
})
172+
173+
const isFetching = useIsFetching()
174+
175+
return (
176+
<div>
177+
<div>isFetching: {isFetching}</div>
178+
</div>
179+
)
180+
}
181+
182+
const rendered = renderWithClient(queryClient, <Page />)
183+
184+
await rendered.findByText('isFetching: 1')
185+
await rendered.findByText('isFetching: 0')
186+
})
162187
})

src/react/useIsFetching.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@ import React from 'react'
33
import { notifyManager } from '../core/notifyManager'
44
import { QueryKey } from '../core/types'
55
import { parseFilterArgs, QueryFilters } from '../core/utils'
6+
import { QueryClient } from '../core'
67
import { useQueryClient } from './QueryClientProvider'
78

9+
const checkIsFetching = (
10+
queryClient: QueryClient,
11+
filters: QueryFilters,
12+
isFetching: number,
13+
setIsFetching: React.Dispatch<React.SetStateAction<number>>
14+
) => {
15+
const newIsFetching = queryClient.isFetching(filters)
16+
if (isFetching !== newIsFetching) {
17+
setIsFetching(newIsFetching)
18+
}
19+
}
20+
821
export function useIsFetching(filters?: QueryFilters): number
922
export function useIsFetching(
1023
queryKey?: QueryKey,
@@ -31,13 +44,22 @@ export function useIsFetching(
3144
React.useEffect(() => {
3245
mountedRef.current = true
3346

47+
checkIsFetching(
48+
queryClient,
49+
filtersRef.current,
50+
isFetchingRef.current,
51+
setIsFetching
52+
)
53+
3454
const unsubscribe = queryClient.getQueryCache().subscribe(
3555
notifyManager.batchCalls(() => {
3656
if (mountedRef.current) {
37-
const newIsFetching = queryClient.isFetching(filtersRef.current)
38-
if (isFetchingRef.current !== newIsFetching) {
39-
setIsFetching(newIsFetching)
40-
}
57+
checkIsFetching(
58+
queryClient,
59+
filtersRef.current,
60+
isFetchingRef.current,
61+
setIsFetching
62+
)
4163
}
4264
})
4365
)

0 commit comments

Comments
 (0)