Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/open-keys-create.md
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
TkDodo marked this conversation as resolved.
Outdated
6 changes: 5 additions & 1 deletion packages/query-core/src/queryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
)
Comment thread
TkDodo marked this conversation as resolved.
Outdated
}
return false
}
Expand Down
108 changes: 108 additions & 0 deletions packages/react-query/src/__tests__/issue-9728.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<div>Data: {data}</div>
<button onClick={() => refetch()}>Refetch</button>
</div>
)
}

function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
<div>Status: error</div>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
)}
>
<React.Suspense fallback={<div>Loading...</div>}>
<Page />
</React.Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
)
}

const { getByText, findByText } = render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
)

// 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)
})
})
Loading