Skip to content

Commit 86bb20a

Browse files
committed
fix: ensure hook subscription failures do not reset isLoading state
1 parent e46eb99 commit 86bb20a

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,10 +687,16 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
687687

688688
const hasData = data !== undefined
689689

690+
// error is the last known error we have tracked - or if none has been tracked yet the last errored result for the current args
691+
let error = currentState.isError ? currentState.error : lastResult?.error
692+
if (error === undefined) error = currentState.error
693+
694+
const hasError = error !== undefined
695+
690696
// isFetching = true any time a request is in flight
691697
const isFetching = currentState.isLoading
692698
// isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
693-
const isLoading = !hasData && isFetching
699+
const isLoading = !hasError && !hasData && isFetching
694700
// isSuccess = true when data is present
695701
const isSuccess = currentState.isSuccess || (isFetching && hasData)
696702

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,57 @@ describe('hooks tests', () => {
873873
}
874874
})
875875

876+
test('Hook subscription failures do not reset isLoading state', async () => {
877+
const states: boolean[] = []
878+
879+
function Parent() {
880+
const { isLoading } = api.endpoints.getUserAndForceError.useQuery(1)
881+
882+
// Collect loading states to verify that it does not revert back to true.
883+
states.push(isLoading)
884+
885+
// Parent conditionally renders child when loading.
886+
if (isLoading) return null
887+
888+
return <Child />
889+
}
890+
891+
function Child() {
892+
// Using the same args as the parent
893+
api.endpoints.getUserAndForceError.useQuery(1)
894+
895+
return null
896+
}
897+
898+
render(<Parent />, { wrapper: storeRef.wrapper })
899+
900+
// Allow at least three state effects to hit.
901+
// Trying to see if any [true, false, true] occurs.
902+
await act(async () => {
903+
await waitMs(1)
904+
})
905+
906+
await act(async () => {
907+
await waitMs(1)
908+
})
909+
910+
await act(async () => {
911+
await waitMs(1)
912+
})
913+
914+
// Find if at any time the isLoading state has reverted
915+
// E.G.: `[..., true, false, ..., true]`
916+
// ^^^^ ^^^^^ ^^^^
917+
const firstTrue = states.indexOf(true)
918+
const firstFalse = states.slice(firstTrue).indexOf(false)
919+
const revertedState = states.slice(firstFalse).indexOf(true)
920+
921+
expect(
922+
revertedState,
923+
`Expected isLoading state to never revert back to true but did after ${revertedState} renders...`,
924+
).toBe(-1)
925+
})
926+
876927
describe('Hook middleware requirements', () => {
877928
let mock: MockInstance
878929

0 commit comments

Comments
 (0)