Skip to content

Commit 6f7fa29

Browse files
committed
fix(core): make sure erroneous queries refetch when query keys change
the change in shouldFetchOptionally was introduced in #2556 to avoid an infinite loop wen using suspense (see #2187), but it broke the case where we're switching keys between two erroneous queries - they now didn't refetch even though they should this fix scopes the error check to only usages where suspense is turned on, so that we get the same behaviour as in v3.19.4 for non-suspense users, while still having the fix for suspense users
1 parent c956f1a commit 6f7fa29

File tree

2 files changed

+82
-17
lines changed

2 files changed

+82
-17
lines changed

src/core/queryObserver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,9 @@ function shouldFetchOptionally(
767767
return (
768768
options.enabled !== false &&
769769
(query !== prevQuery || prevOptions.enabled === false) &&
770-
(query.state.status !== 'error' || prevOptions.enabled === false) &&
770+
(!options.suspense ||
771+
query.state.status !== 'error' ||
772+
prevOptions.enabled === false) &&
771773
isStale(query, options)
772774
)
773775
}

src/react/tests/useQuery.test.tsx

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4056,23 +4056,25 @@ describe('useQuery', () => {
40564056

40574057
it('should refetch when query key changed when previous status is error', async () => {
40584058
const consoleMock = mockConsoleError()
4059-
const queryFn = jest.fn()
40604059

40614060
function Page({ id }: { id: number }) {
4062-
queryFn.mockImplementation(async () => {
4063-
await sleep(10)
4064-
if (id % 2 === 1) {
4065-
return Promise.reject(new Error('Suspense Error Bingo'))
4066-
} else {
4067-
return 'data'
4061+
const { error, isLoading } = useQuery(
4062+
[id],
4063+
async () => {
4064+
await sleep(10)
4065+
if (id % 2 === 1) {
4066+
return Promise.reject(new Error('Error'))
4067+
} else {
4068+
return 'data'
4069+
}
4070+
},
4071+
{
4072+
retry: false,
4073+
retryOnMount: false,
4074+
refetchOnMount: false,
4075+
refetchOnWindowFocus: false,
40684076
}
4069-
})
4070-
const { error, isLoading } = useQuery([id], queryFn, {
4071-
retry: false,
4072-
retryOnMount: false,
4073-
refetchOnMount: false,
4074-
refetchOnWindowFocus: false,
4075-
})
4077+
)
40764078

40774079
if (isLoading) {
40784080
return <div>status: loading</div>
@@ -4104,12 +4106,73 @@ describe('useQuery', () => {
41044106
// render error state component
41054107
await waitFor(() => rendered.getByText('error'))
41064108

4107-
// change to enabled to false
4109+
// change to unmount query
41084110
fireEvent.click(rendered.getByLabelText('change'))
41094111
await waitFor(() => rendered.getByText('rendered'))
41104112

4111-
// // change to enabled to true
4113+
// change to mount new query
4114+
fireEvent.click(rendered.getByLabelText('change'))
4115+
await waitFor(() => rendered.getByText('error'))
4116+
4117+
consoleMock.mockRestore()
4118+
})
4119+
4120+
it('should refetch when query key changed when switching between erroneous queries', async () => {
4121+
const consoleMock = mockConsoleError()
4122+
4123+
function Page({ id }: { id: boolean }) {
4124+
const { error, isFetching } = useQuery(
4125+
[id],
4126+
async () => {
4127+
await sleep(10)
4128+
return Promise.reject(new Error('Error'))
4129+
},
4130+
{
4131+
retry: false,
4132+
retryOnMount: false,
4133+
refetchOnMount: false,
4134+
refetchOnWindowFocus: false,
4135+
}
4136+
)
4137+
4138+
if (isFetching) {
4139+
return <div>status: fetching</div>
4140+
}
4141+
if (error instanceof Error) {
4142+
return <div>error</div>
4143+
}
4144+
return <div>rendered</div>
4145+
}
4146+
4147+
function App() {
4148+
const [value, toggle] = React.useReducer(x => !x, true)
4149+
4150+
return (
4151+
<div>
4152+
<Page id={value} />
4153+
<button aria-label="change" onClick={toggle}>
4154+
change {value}
4155+
</button>
4156+
</div>
4157+
)
4158+
}
4159+
4160+
const rendered = renderWithClient(queryClient, <App />)
4161+
4162+
// initial state check
4163+
rendered.getByText('status: fetching')
4164+
4165+
// render error state component
4166+
await waitFor(() => rendered.getByText('error'))
4167+
4168+
// change to mount second query
4169+
fireEvent.click(rendered.getByLabelText('change'))
4170+
await waitFor(() => rendered.getByText('status: fetching'))
4171+
await waitFor(() => rendered.getByText('error'))
4172+
4173+
// change to mount first query again
41124174
fireEvent.click(rendered.getByLabelText('change'))
4175+
await waitFor(() => rendered.getByText('status: fetching'))
41134176
await waitFor(() => rendered.getByText('error'))
41144177

41154178
consoleMock.mockRestore()

0 commit comments

Comments
 (0)