Skip to content

Commit ead2e5d

Browse files
authored
fix(query): transition to error state when keepPreviousData is true (#1665)
closes #1659
1 parent eb3d700 commit ead2e5d

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

src/core/queryObserver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ export class QueryObserver<
368368
if (
369369
this.options.keepPreviousData &&
370370
!state.dataUpdateCount &&
371-
this.previousQueryResult?.isSuccess
371+
this.previousQueryResult?.isSuccess &&
372+
status !== 'error'
372373
) {
373374
data = this.previousQueryResult.data
374375
dataUpdatedAt = this.previousQueryResult.dataUpdatedAt

src/react/tests/useQuery.test.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,112 @@ describe('useQuery', () => {
11231123
})
11241124
})
11251125

1126+
it('should transition to error state when keepPreviousData is set', async () => {
1127+
const key = queryKey()
1128+
const consoleMock = mockConsoleError()
1129+
const states: UseQueryResult<number>[] = []
1130+
1131+
function Page({ count }: { count: number }) {
1132+
const state = useQuery<number, Error>(
1133+
[key, count],
1134+
async () => {
1135+
if (count === 2) {
1136+
throw new Error('Error test')
1137+
}
1138+
return Promise.resolve(count)
1139+
},
1140+
{
1141+
retry: false,
1142+
keepPreviousData: true,
1143+
}
1144+
)
1145+
1146+
states.push(state)
1147+
1148+
return (
1149+
<div>
1150+
<h1>data: {state.data}</h1>
1151+
<h2>error: {state.error?.message}</h2>
1152+
</div>
1153+
)
1154+
}
1155+
1156+
const rendered = renderWithClient(queryClient, <Page count={0} />)
1157+
await waitFor(() => rendered.getByText('data: 0'))
1158+
act(() => rendered.rerender(<Page count={1} />))
1159+
await waitFor(() => rendered.getByText('data: 1'))
1160+
act(() => rendered.rerender(<Page count={2} />))
1161+
await waitFor(() => rendered.getByText('error: Error test'))
1162+
1163+
expect(states.length).toBe(8)
1164+
// Initial
1165+
expect(states[0]).toMatchObject({
1166+
data: undefined,
1167+
isFetching: true,
1168+
status: 'loading',
1169+
error: null,
1170+
isPreviousData: false,
1171+
})
1172+
// Fetched
1173+
expect(states[1]).toMatchObject({
1174+
data: 0,
1175+
isFetching: false,
1176+
status: 'success',
1177+
error: null,
1178+
isPreviousData: false,
1179+
})
1180+
// rerender Page 1
1181+
expect(states[2]).toMatchObject({
1182+
data: 0,
1183+
isFetching: true,
1184+
status: 'success',
1185+
error: null,
1186+
isPreviousData: true,
1187+
})
1188+
// Hook state update
1189+
expect(states[3]).toMatchObject({
1190+
data: 0,
1191+
isFetching: true,
1192+
status: 'success',
1193+
error: null,
1194+
isPreviousData: true,
1195+
})
1196+
// New data
1197+
expect(states[4]).toMatchObject({
1198+
data: 1,
1199+
isFetching: false,
1200+
status: 'success',
1201+
error: null,
1202+
isPreviousData: false,
1203+
})
1204+
// rerender Page 2
1205+
expect(states[5]).toMatchObject({
1206+
data: 1,
1207+
isFetching: true,
1208+
status: 'success',
1209+
error: null,
1210+
isPreviousData: true,
1211+
})
1212+
// Hook state update again
1213+
expect(states[6]).toMatchObject({
1214+
data: 1,
1215+
isFetching: true,
1216+
status: 'success',
1217+
error: null,
1218+
isPreviousData: true,
1219+
})
1220+
// Error
1221+
expect(states[7]).toMatchObject({
1222+
data: undefined,
1223+
isFetching: false,
1224+
status: 'error',
1225+
isPreviousData: false,
1226+
})
1227+
expect(states[7].error).toHaveProperty('message', 'Error test')
1228+
1229+
consoleMock.mockRestore()
1230+
})
1231+
11261232
it('should not show initial data from next query if keepPreviousData is set', async () => {
11271233
const key = queryKey()
11281234
const states: UseQueryResult<number>[] = []

src/react/tests/utils.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ import React from 'react'
44
import { QueryClient, QueryClientProvider } from '../..'
55

66
export function renderWithClient(client: QueryClient, ui: React.ReactElement) {
7-
return render(<QueryClientProvider client={client}>{ui}</QueryClientProvider>)
7+
const { rerender, ...result } = render(
8+
<QueryClientProvider client={client}>{ui}</QueryClientProvider>
9+
)
10+
return {
11+
...result,
12+
rerender: (rerenderUi: React.ReactElement) =>
13+
rerender(
14+
<QueryClientProvider client={client}>{rerenderUi}</QueryClientProvider>
15+
),
16+
}
817
}
918

1019
export function mockVisibilityState(value: string) {

0 commit comments

Comments
 (0)