Skip to content

Commit bac853f

Browse files
authored
fix(core): change shouldFetchOptionally function to account for query key changes (TanStack#2565)
1 parent 69f1eaf commit bac853f

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

src/core/queryObserver.ts

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

src/react/tests/suspense.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,4 +647,68 @@ describe("useQuery's in Suspense mode", () => {
647647
await waitFor(() => rendered.getByText('error boundary'))
648648
consoleMock.mockRestore()
649649
})
650+
651+
it('should error catched in error boundary without infinite loop when query keys changed', async () => {
652+
const consoleMock = mockConsoleError()
653+
654+
let succeed = true
655+
656+
function Page() {
657+
const [key, rerender] = React.useReducer(x => x + 1, 0)
658+
const queryKeys = [key, succeed]
659+
660+
const result = useQuery(
661+
queryKeys,
662+
async () => {
663+
await sleep(10)
664+
if (!succeed) {
665+
throw new Error('Suspense Error Bingo')
666+
} else {
667+
return 'data'
668+
}
669+
},
670+
{
671+
retry: false,
672+
suspense: true,
673+
}
674+
)
675+
return (
676+
<div>
677+
<span>rendered</span> <span>{result.data}</span>
678+
<button aria-label="fail" onClick={rerender}>
679+
fail
680+
</button>
681+
</div>
682+
)
683+
}
684+
685+
function App() {
686+
const { reset } = useQueryErrorResetBoundary()
687+
return (
688+
<ErrorBoundary
689+
onReset={reset}
690+
fallbackRender={() => <div>error boundary</div>}
691+
>
692+
<React.Suspense fallback="Loading...">
693+
<Page />
694+
</React.Suspense>
695+
</ErrorBoundary>
696+
)
697+
}
698+
699+
const rendered = renderWithClient(queryClient, <App />)
700+
701+
// render suspense fallback (Loading...)
702+
await waitFor(() => rendered.getByText('Loading...'))
703+
// resolve promise -> render Page (rendered)
704+
await waitFor(() => rendered.getByText('rendered'))
705+
706+
// change promise result to error
707+
succeed = false
708+
// change query key
709+
fireEvent.click(rendered.getByLabelText('fail'))
710+
// render error boundary fallback (error boundary)
711+
await waitFor(() => rendered.getByText('error boundary'))
712+
consoleMock.mockRestore()
713+
})
650714
})

src/react/tests/useQuery.test.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3944,4 +3944,65 @@ describe('useQuery', () => {
39443944

39453945
consoleMock.mockRestore()
39463946
})
3947+
3948+
it('should refetch when query key changed when previous status is error', async () => {
3949+
const consoleMock = mockConsoleError()
3950+
const queryFn = jest.fn()
3951+
3952+
function Page({ id }: { id: number }) {
3953+
queryFn.mockImplementation(async () => {
3954+
await sleep(10)
3955+
if (id % 2 === 1) {
3956+
return Promise.reject(new Error('Suspense Error Bingo'))
3957+
} else {
3958+
return 'data'
3959+
}
3960+
})
3961+
const { error, isLoading } = useQuery([id], queryFn, {
3962+
retry: false,
3963+
retryOnMount: false,
3964+
refetchOnMount: false,
3965+
refetchOnWindowFocus: false,
3966+
})
3967+
3968+
if (isLoading) {
3969+
return <div>status: loading</div>
3970+
}
3971+
if (error instanceof Error) {
3972+
return <div>error</div>
3973+
}
3974+
return <div>rendered</div>
3975+
}
3976+
3977+
function App() {
3978+
const [id, changeId] = React.useReducer(x => x + 1, 1)
3979+
3980+
return (
3981+
<div>
3982+
<Page id={id} />
3983+
<button aria-label="change" onClick={changeId}>
3984+
change {id}
3985+
</button>
3986+
</div>
3987+
)
3988+
}
3989+
3990+
const rendered = renderWithClient(queryClient, <App />)
3991+
3992+
// initial state check
3993+
rendered.getByText('status: loading')
3994+
3995+
// render error state component
3996+
await waitFor(() => rendered.getByText('error'))
3997+
3998+
// change to enabled to false
3999+
fireEvent.click(rendered.getByLabelText('change'))
4000+
await waitFor(() => rendered.getByText('rendered'))
4001+
4002+
// // change to enabled to true
4003+
fireEvent.click(rendered.getByLabelText('change'))
4004+
await waitFor(() => rendered.getByText('error'))
4005+
4006+
consoleMock.mockRestore()
4007+
})
39474008
})

0 commit comments

Comments
 (0)