Skip to content

Commit 69f1eaf

Browse files
authored
fix(core): change shouldFetchOptionally condition (TanStack#2556)
1 parent 6bbb371 commit 69f1eaf

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

src/core/queryObserver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ function shouldFetchOptionally(
763763
return (
764764
options.enabled !== false &&
765765
(query !== prevQuery || prevOptions.enabled === false) &&
766+
(prevQuery.state.status !== 'error' || prevOptions.enabled === false) &&
766767
isStale(query, options)
767768
)
768769
}

src/react/tests/suspense.test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,4 +577,74 @@ describe("useQuery's in Suspense mode", () => {
577577
expect(queryFn).toHaveBeenCalledTimes(1)
578578
await waitFor(() => rendered.getByLabelText('fire'))
579579
})
580+
581+
it('should error catched in error boundary without infinite loop', async () => {
582+
const key = queryKey()
583+
584+
const consoleMock = mockConsoleError()
585+
586+
let succeed = true
587+
588+
function Page() {
589+
const [nonce] = React.useState(0)
590+
const queryKeys = `${key}-${succeed}`
591+
const result = useQuery(
592+
queryKeys,
593+
async () => {
594+
await sleep(10)
595+
if (!succeed) {
596+
throw new Error('Suspense Error Bingo')
597+
} else {
598+
return nonce
599+
}
600+
},
601+
{
602+
retry: false,
603+
suspense: true,
604+
}
605+
)
606+
return (
607+
<div>
608+
<span>rendered</span> <span>{result.data}</span>
609+
<button
610+
aria-label="fail"
611+
onClick={async () => {
612+
await queryClient.resetQueries()
613+
}}
614+
>
615+
fail
616+
</button>
617+
</div>
618+
)
619+
}
620+
621+
function App() {
622+
const { reset } = useQueryErrorResetBoundary()
623+
return (
624+
<ErrorBoundary
625+
onReset={reset}
626+
fallbackRender={() => <div>error boundary</div>}
627+
>
628+
<React.Suspense fallback="Loading...">
629+
<Page />
630+
</React.Suspense>
631+
</ErrorBoundary>
632+
)
633+
}
634+
635+
const rendered = renderWithClient(queryClient, <App />)
636+
637+
// render suspense fallback (Loading...)
638+
await waitFor(() => rendered.getByText('Loading...'))
639+
// resolve promise -> render Page (rendered)
640+
await waitFor(() => rendered.getByText('rendered'))
641+
642+
// change query key
643+
succeed = false
644+
// reset query -> and throw error
645+
fireEvent.click(rendered.getByLabelText('fail'))
646+
// render error boundary fallback (error boundary)
647+
await waitFor(() => rendered.getByText('error boundary'))
648+
consoleMock.mockRestore()
649+
})
580650
})

src/react/tests/useQuery.test.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3883,4 +3883,65 @@ describe('useQuery', () => {
38833883
expect(renders).toBe(2)
38843884
expect(hashes).toBe(2)
38853885
})
3886+
3887+
it('should refetch when changed enabled to true in error state', async () => {
3888+
const consoleMock = mockConsoleError()
3889+
3890+
const queryFn = jest.fn()
3891+
queryFn.mockImplementation(async () => {
3892+
await sleep(10)
3893+
return Promise.reject(new Error('Suspense Error Bingo'))
3894+
})
3895+
3896+
function Page({ enabled }: { enabled: boolean }) {
3897+
const { error, isLoading } = useQuery(['key'], queryFn, {
3898+
enabled,
3899+
retry: false,
3900+
retryOnMount: false,
3901+
refetchOnMount: false,
3902+
refetchOnWindowFocus: false,
3903+
})
3904+
3905+
if (isLoading) {
3906+
return <div>status: loading</div>
3907+
}
3908+
if (error instanceof Error) {
3909+
return <div>error</div>
3910+
}
3911+
return <div>rendered</div>
3912+
}
3913+
3914+
function App() {
3915+
const [enabled, toggle] = React.useReducer(x => !x, true)
3916+
3917+
return (
3918+
<div>
3919+
<Page enabled={enabled} />
3920+
<button aria-label="retry" onClick={toggle}>
3921+
retry {enabled}
3922+
</button>
3923+
</div>
3924+
)
3925+
}
3926+
3927+
const rendered = renderWithClient(queryClient, <App />)
3928+
3929+
// initial state check
3930+
rendered.getByText('status: loading')
3931+
3932+
// // render error state component
3933+
await waitFor(() => rendered.getByText('error'))
3934+
expect(queryFn).toBeCalledTimes(1)
3935+
3936+
// change to enabled to false
3937+
fireEvent.click(rendered.getByLabelText('retry'))
3938+
await waitFor(() => rendered.getByText('error'))
3939+
expect(queryFn).toBeCalledTimes(1)
3940+
3941+
// // change to enabled to true
3942+
fireEvent.click(rendered.getByLabelText('retry'))
3943+
expect(queryFn).toBeCalledTimes(2)
3944+
3945+
consoleMock.mockRestore()
3946+
})
38863947
})

0 commit comments

Comments
 (0)