Skip to content

Commit 4d92026

Browse files
authored
Merge pull request #4731 from reduxjs/bugfix/3353-isSuccess-flashing
Keep `isSuccess: true` when switching to an uninitialized cache entry
2 parents 22cb3d0 + 02248d0 commit 4d92026

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,13 +927,19 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
927927

928928
// isFetching = true any time a request is in flight
929929
const isFetching = currentState.isLoading
930+
930931
// isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
931932
const isLoading =
932933
(!lastResult || lastResult.isLoading || lastResult.isUninitialized) &&
933934
!hasData &&
934935
isFetching
935-
// isSuccess = true when data is present
936-
const isSuccess = currentState.isSuccess || (isFetching && hasData)
936+
937+
// isSuccess = true when data is present.
938+
// That includes cases where the _current_ item is either actively
939+
// fetching or about to fetch due to an uninitialized entry.
940+
const isSuccess =
941+
currentState.isSuccess ||
942+
((isFetching || currentState.isUninitialized) && hasData)
937943

938944
return {
939945
...currentState,

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

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ const api = createApi({
126126
},
127127
}),
128128
queryWithDeepArg: build.query<string, { param: { nested: string } }>({
129-
query: ({ param: { nested }}) => nested,
129+
query: ({ param: { nested } }) => nested,
130130
serializeQueryArgs: ({ queryArgs }) => {
131131
return queryArgs.param.nested
132-
}
132+
},
133133
}),
134134
}),
135135
})
@@ -383,6 +383,54 @@ describe('hooks tests', () => {
383383
expect(fetchingHist).toEqual([true, false, true, false])
384384
})
385385

386+
test('`isSuccess` does not jump back false on subsequent queries', async () => {
387+
type LoadingState = {
388+
id: number
389+
isFetching: boolean
390+
isSuccess: boolean
391+
}
392+
const loadingHistory: LoadingState[] = []
393+
394+
function User({ id }: { id: number }) {
395+
const queryRes = api.endpoints.getUser.useQuery(id)
396+
397+
useEffect(() => {
398+
const { isFetching, isSuccess } = queryRes
399+
loadingHistory.push({ id, isFetching, isSuccess })
400+
}, [id, queryRes])
401+
return (
402+
<div data-testid="status">
403+
{queryRes.status === QueryStatus.fulfilled && id}
404+
</div>
405+
)
406+
}
407+
408+
let { rerender } = render(<User id={1} />, { wrapper: storeRef.wrapper })
409+
410+
await waitFor(() =>
411+
expect(screen.getByTestId('status').textContent).toBe('1'),
412+
)
413+
rerender(<User id={2} />)
414+
415+
await waitFor(() =>
416+
expect(screen.getByTestId('status').textContent).toBe('2'),
417+
)
418+
419+
expect(loadingHistory).toEqual([
420+
// Initial render(s)
421+
{ id: 1, isFetching: true, isSuccess: false },
422+
{ id: 1, isFetching: true, isSuccess: false },
423+
// Data returned
424+
{ id: 1, isFetching: false, isSuccess: true },
425+
// ID changed, there's an uninitialized cache entry.
426+
// IMPORTANT: `isSuccess` should not be false here.
427+
// We have valid data already for the old item.
428+
{ id: 2, isFetching: true, isSuccess: true },
429+
{ id: 2, isFetching: true, isSuccess: true },
430+
{ id: 2, isFetching: false, isSuccess: true },
431+
])
432+
})
433+
386434
test('useQuery hook respects refetchOnMountOrArgChange: true', async () => {
387435
let data, isLoading, isFetching
388436
function User() {
@@ -674,9 +722,11 @@ describe('hooks tests', () => {
674722
})
675723

676724
test(`useQuery shouldn't call args serialization if request skipped`, async () => {
677-
expect(() => renderHook(() => api.endpoints.queryWithDeepArg.useQuery(skipToken), {
678-
wrapper: storeRef.wrapper,
679-
})).not.toThrow()
725+
expect(() =>
726+
renderHook(() => api.endpoints.queryWithDeepArg.useQuery(skipToken), {
727+
wrapper: storeRef.wrapper,
728+
}),
729+
).not.toThrow()
680730
})
681731

682732
test(`useQuery gracefully handles bigint types`, async () => {
@@ -2861,6 +2911,7 @@ describe('skip behavior', () => {
28612911
})
28622912
expect(result.current).toEqual({
28632913
...uninitialized,
2914+
isSuccess: true,
28642915
currentData: undefined,
28652916
data: { name: 'Timmy' },
28662917
})
@@ -2898,6 +2949,7 @@ describe('skip behavior', () => {
28982949
})
28992950
expect(result.current).toEqual({
29002951
...uninitialized,
2952+
isSuccess: true,
29012953
currentData: undefined,
29022954
data: { name: 'Timmy' },
29032955
})
@@ -2936,7 +2988,7 @@ describe('skip behavior', () => {
29362988
// even though it's skipped. `currentData` is undefined, since that matches the current arg.
29372989
expect(result.current).toMatchObject({
29382990
status: QueryStatus.uninitialized,
2939-
isSuccess: false,
2991+
isSuccess: true,
29402992
data: { name: 'Timmy' },
29412993
currentData: undefined,
29422994
})

0 commit comments

Comments
 (0)