diff --git a/.changeset/open-keys-create.md b/.changeset/open-keys-create.md new file mode 100644 index 0000000000..5b66e68ae3 --- /dev/null +++ b/.changeset/open-keys-create.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-core': patch +--- + +Fix: Ensure queries refetch on mount or retry when in error state, even if data is not stale. diff --git a/packages/query-core/src/queryObserver.ts b/packages/query-core/src/queryObserver.ts index 92978673f6..0d22724ec2 100644 --- a/packages/query-core/src/queryObserver.ts +++ b/packages/query-core/src/queryObserver.ts @@ -781,7 +781,11 @@ function shouldFetchOn( ) { const value = typeof field === 'function' ? field(query) : field - return value === 'always' || (value !== false && isStale(query, options)) + return ( + value === 'always' || + (value !== false && + (isStale(query, options) || query.state.status === 'error')) + ) } return false } diff --git a/packages/react-query/src/__tests__/issue-9728.test.tsx b/packages/react-query/src/__tests__/issue-9728.test.tsx new file mode 100644 index 0000000000..a12a437025 --- /dev/null +++ b/packages/react-query/src/__tests__/issue-9728.test.tsx @@ -0,0 +1,108 @@ +// @vitest-environment jsdom +import { describe, expect, it, vi } from 'vitest' +import { fireEvent, render } from '@testing-library/react' +import * as React from 'react' +import { ErrorBoundary } from 'react-error-boundary' +import { + QueryClient, + QueryClientProvider, + QueryErrorResetBoundary, + useQuery, +} from '..' +import { queryKey } from '@tanstack/query-test-utils' + +describe('issue 9728', () => { + it('should refetch after error when staleTime is Infinity and previous data exists', async () => { + const key = queryKey() + const queryFn = vi.fn() + let count = 0 + + queryFn.mockImplementation(async () => { + count++ + if (count === 2) { + throw new Error('Error ' + count) + } + return 'Success ' + count + }) + + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Infinity, + }, + }, + }) + + function Page() { + const [_, forceUpdate] = React.useState(0) + + React.useEffect(() => { + forceUpdate(1) + }, []) + + const { data, refetch } = useQuery({ + queryKey: key, + queryFn, + throwOnError: true, + }) + + return ( +
+
Data: {data}
+ +
+ ) + } + + function App() { + return ( + + {({ reset }) => ( + ( +
+
Status: error
+ +
+ )} + > + Loading...}> + + +
+ )} +
+ ) + } + + const { getByText, findByText } = render( + + + + + , + ) + + // 1. First mount -> Success + await findByText('Data: Success 1') + expect(queryFn).toHaveBeenCalledTimes(1) + + // 2. Click Refetch -> Triggers fetch -> Fails (Error 2) -> ErrorBoundary + fireEvent.click(getByText('Refetch')) + + // Wait for error UI + await findByText('Status: error') + expect(queryFn).toHaveBeenCalledTimes(2) + + // 3. Click Retry -> Remounts + // Because staleTime is Infinity and we have Data from (1), + // AND we are in Error state. + fireEvent.click(getByText('Retry')) + + // Should call queryFn again (3rd time) and succeed + await findByText('Data: Success 3') + expect(queryFn).toHaveBeenCalledTimes(3) + }) +})