Skip to content

Commit 38a9316

Browse files
authored
fix "isLoading briefly flips back to true" #1519 (#1520)
1 parent 97319a3 commit 38a9316

File tree

2 files changed

+63
-26
lines changed

2 files changed

+63
-26
lines changed

packages/toolkit/src/query/react/buildHooks.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -406,16 +406,17 @@ const queryStatePreSelector = (
406406
lastResult: UseQueryStateDefaultResult<any>
407407
): UseQueryStateDefaultResult<any> => {
408408
// data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args
409-
const data =
410-
(currentState.isSuccess ? currentState.data : lastResult?.data) ??
411-
currentState.data
409+
let data = currentState.isSuccess ? currentState.data : lastResult?.data
410+
if (data === undefined) data = currentState.data
411+
412+
const hasData = data !== undefined
412413

413414
// isFetching = true any time a request is in flight
414415
const isFetching = currentState.isLoading
415416
// isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
416-
const isLoading = !data && isFetching
417+
const isLoading = !hasData && isFetching
417418
// isSuccess = true when data is present
418-
const isSuccess = currentState.isSuccess || (isFetching && !!data)
419+
const isSuccess = currentState.isSuccess || (isFetching && hasData)
419420

420421
return {
421422
...currentState,
@@ -440,7 +441,7 @@ const noPendingQueryStateSelector: QueryStateSelector<any, any> = (
440441
...selected,
441442
isUninitialized: false,
442443
isFetching: true,
443-
isLoading: true,
444+
isLoading: selected.data !== undefined ? false : true,
444445
status: QueryStatus.pending,
445446
} as any
446447
}

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ const api = createApi({
4444
}
4545

4646
return {
47-
data: arg?.body
48-
? { ...arg.body, ...(amount ? { amount } : {}) }
49-
: undefined,
47+
data: arg?.body ? { ...arg.body, ...(amount ? { amount } : {}) } : {},
5048
}
5149
},
5250
endpoints: (build) => ({
@@ -146,16 +144,20 @@ describe('hooks tests', () => {
146144
})
147145

148146
test('useQuery hook sets isLoading=true only on initial request', async () => {
149-
let refetch: any, isLoading: boolean
147+
let refetch: any, isLoading: boolean, isFetching: boolean
150148
function User() {
151149
const [value, setValue] = React.useState(0)
152150

153-
;({ isLoading, refetch } = api.endpoints.getUser.useQuery(2, {
154-
skip: value < 1,
155-
}))
151+
;({ isLoading, isFetching, refetch } = api.endpoints.getUser.useQuery(
152+
2,
153+
{
154+
skip: value < 1,
155+
}
156+
))
156157
return (
157158
<div>
158159
<div data-testid="isLoading">{String(isLoading)}</div>
160+
<div data-testid="isFetching">{String(isFetching)}</div>
159161
<button onClick={() => setValue((val) => val + 1)}>
160162
Increment value
161163
</button>
@@ -182,14 +184,12 @@ describe('hooks tests', () => {
182184
await waitFor(() =>
183185
expect(screen.getByTestId('isLoading').textContent).toBe('false')
184186
)
185-
// We call a refetch, should set to true
187+
// We call a refetch, should still be `false`
186188
act(() => refetch())
187189
await waitFor(() =>
188-
expect(screen.getByTestId('isLoading').textContent).toBe('true')
189-
)
190-
await waitFor(() =>
191-
expect(screen.getByTestId('isLoading').textContent).toBe('false')
190+
expect(screen.getByTestId('isFetching').textContent).toBe('true')
192191
)
192+
expect(screen.getByTestId('isLoading').textContent).toBe('false')
193193
})
194194

195195
test('useQuery hook sets isLoading and isFetching to the correct states', async () => {
@@ -240,10 +240,10 @@ describe('hooks tests', () => {
240240
})
241241
expect(getRenderCount()).toBe(5)
242242

243-
// We call a refetch, should set both to true, then false when complete/errored
243+
// We call a refetch, should set `isFetching` to true, then false when complete/errored
244244
act(() => refetchMe())
245245
await waitFor(() => {
246-
expect(screen.getByTestId('isLoading').textContent).toBe('true')
246+
expect(screen.getByTestId('isLoading').textContent).toBe('false')
247247
expect(screen.getByTestId('isFetching').textContent).toBe('true')
248248
})
249249
await waitFor(() => {
@@ -253,6 +253,42 @@ describe('hooks tests', () => {
253253
expect(getRenderCount()).toBe(7)
254254
})
255255

256+
test('`isLoading` does not jump back to true, while `isFetching` does', async () => {
257+
const loadingHist: boolean[] = [],
258+
fetchingHist: boolean[] = []
259+
260+
function User({ id }: { id: number }) {
261+
const { isLoading, isFetching, status } =
262+
api.endpoints.getUser.useQuery(id)
263+
264+
React.useEffect(() => {
265+
loadingHist.push(isLoading)
266+
}, [isLoading])
267+
React.useEffect(() => {
268+
fetchingHist.push(isFetching)
269+
}, [isFetching])
270+
return (
271+
<div data-testid="status">
272+
{status === QueryStatus.fulfilled && id}
273+
</div>
274+
)
275+
}
276+
277+
let { rerender } = render(<User id={1} />, { wrapper: storeRef.wrapper })
278+
279+
await waitFor(() =>
280+
expect(screen.getByTestId('status').textContent).toBe('1')
281+
)
282+
rerender(<User id={2} />)
283+
284+
await waitFor(() =>
285+
expect(screen.getByTestId('status').textContent).toBe('2')
286+
)
287+
288+
expect(loadingHist).toEqual([true, false])
289+
expect(fetchingHist).toEqual([true, false, true, false])
290+
})
291+
256292
test('useQuery hook respects refetchOnMountOrArgChange: true', async () => {
257293
let data, isLoading, isFetching
258294
function User() {
@@ -903,7 +939,7 @@ describe('hooks tests', () => {
903939
expect(
904940
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
905941
).toEqual({
906-
data: undefined,
942+
data: {},
907943
endpointName: 'getUser',
908944
error: undefined,
909945
fulfilledTimeStamp: expect.any(Number),
@@ -924,7 +960,7 @@ describe('hooks tests', () => {
924960
expect(
925961
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
926962
).toEqual({
927-
data: undefined,
963+
data: {},
928964
endpointName: 'getUser',
929965
fulfilledTimeStamp: expect.any(Number),
930966
isError: false,
@@ -972,7 +1008,7 @@ describe('hooks tests', () => {
9721008
expect(
9731009
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
9741010
).toEqual({
975-
data: undefined,
1011+
data: {},
9761012
endpointName: 'getUser',
9771013
fulfilledTimeStamp: expect.any(Number),
9781014
isError: false,
@@ -990,7 +1026,7 @@ describe('hooks tests', () => {
9901026
expect(
9911027
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
9921028
).toEqual({
993-
data: undefined,
1029+
data: {},
9941030
endpointName: 'getUser',
9951031
fulfilledTimeStamp: expect.any(Number),
9961032
isError: false,
@@ -1040,7 +1076,7 @@ describe('hooks tests', () => {
10401076
expect(
10411077
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
10421078
).toEqual({
1043-
data: undefined,
1079+
data: {},
10441080
endpointName: 'getUser',
10451081
fulfilledTimeStamp: expect.any(Number),
10461082
isError: false,
@@ -1060,7 +1096,7 @@ describe('hooks tests', () => {
10601096
expect(
10611097
api.endpoints.getUser.select(USER_ID)(storeRef.store.getState() as any)
10621098
).toEqual({
1063-
data: undefined,
1099+
data: {},
10641100
endpointName: 'getUser',
10651101
fulfilledTimeStamp: expect.any(Number),
10661102
isError: false,

0 commit comments

Comments
 (0)