Skip to content

Commit a4c8c21

Browse files
committed
refactor: move prevent set state in render logic to useIsFetching hook
1 parent 58acca1 commit a4c8c21

File tree

3 files changed

+48
-19
lines changed

3 files changed

+48
-19
lines changed

src/core/queryCache.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -268,17 +268,7 @@ export class QueryCache {
268268

269269
if (!this.config.frozen) {
270270
this.queries[queryHash] = query
271-
272-
if (isServer) {
273-
this.notifyGlobalListeners()
274-
} else {
275-
// Here, we setTimeout so as to not trigger
276-
// any setState's in parent components in the
277-
// middle of the render phase.
278-
setTimeout(() => {
279-
this.notifyGlobalListeners()
280-
})
281-
}
271+
this.notifyGlobalListeners()
282272
}
283273
}
284274

src/react/tests/useIsFetching.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react'
33

44
import { useQuery, useIsFetching } from '../index'
55
import { sleep, queryKey } from './utils'
6+
import { useQueryCache } from '../ReactQueryCacheProvider'
67

78
describe('useIsFetching', () => {
89
// See https://github.com/tannerlinsley/react-query/issues/105
@@ -40,4 +41,46 @@ describe('useIsFetching', () => {
4041
await waitFor(() => rendered.getByText('isFetching: 1'))
4142
await waitFor(() => rendered.getByText('isFetching: 0'))
4243
})
44+
45+
it('should not update state while rendering', async () => {
46+
const spy = jest.spyOn(console, 'error')
47+
48+
const key1 = queryKey()
49+
const key2 = queryKey()
50+
const key3 = queryKey()
51+
52+
const isFetchings: number[] = []
53+
54+
function Child() {
55+
const isFetching = useIsFetching()
56+
isFetchings.push(isFetching)
57+
return null
58+
}
59+
60+
function Page() {
61+
const queryCache = useQueryCache()
62+
63+
useQuery(key1, async () => {
64+
await sleep(100)
65+
return 'data'
66+
})
67+
68+
useQuery(key2, async () => {
69+
await sleep(10)
70+
return 'data'
71+
})
72+
73+
if (isFetchings.length === 1) {
74+
queryCache.setQueryData(key3, () => 'data')
75+
}
76+
77+
return <Child />
78+
}
79+
80+
render(<Page />)
81+
await waitFor(() => expect(isFetchings).toEqual([2, 2, 1, 1, 1, 0]))
82+
expect(spy.mock.calls[0]?.[0] ?? '').not.toMatch('setState')
83+
84+
spy.mockRestore()
85+
})
4386
})

src/react/useIsFetching.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import React from 'react'
22

3-
import { useRerenderer, useGetLatest } from './utils'
43
import { useQueryCache } from './ReactQueryCacheProvider'
54

65
export function useIsFetching(): number {
76
const queryCache = useQueryCache()
8-
const rerender = useRerenderer()
9-
const isFetching = queryCache.isFetching
107

11-
const getIsFetching = useGetLatest(isFetching)
8+
const [isFetching, setIsFetching] = React.useState(queryCache.isFetching)
129

1310
React.useEffect(
1411
() =>
1512
queryCache.subscribe(newCache => {
16-
if (getIsFetching() !== newCache.isFetching) {
17-
rerender()
18-
}
13+
// scheduele microtask to prevent updating state while rendering
14+
Promise.resolve().then(() => setIsFetching(newCache.isFetching))
1915
}),
20-
[getIsFetching, queryCache, rerender]
16+
[queryCache]
2117
)
2218

2319
return isFetching

0 commit comments

Comments
 (0)