diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx
index 893abb03b6..87faf34399 100644
--- a/packages/react-query/src/__tests__/useQuery.test.tsx
+++ b/packages/react-query/src/__tests__/useQuery.test.tsx
@@ -6027,6 +6027,7 @@ describe('useQuery', () => {
it('should be able to toggle subscribed', async () => {
const key = queryKey()
const queryFn = vi.fn(() => Promise.resolve('data'))
+
function Page() {
const [subscribed, setSubscribed] = React.useState(true)
const { data } = useQuery({
@@ -6069,6 +6070,7 @@ describe('useQuery', () => {
it('should not be attached to the query when subscribed is false', async () => {
const key = queryKey()
const queryFn = vi.fn(() => Promise.resolve('data'))
+
function Page() {
const { data } = useQuery({
queryKey: key,
@@ -6095,6 +6097,7 @@ describe('useQuery', () => {
it('should not re-render when data is added to the cache when subscribed is false', async () => {
const key = queryKey()
let renders = 0
+
function Page() {
const { data } = useQuery({
queryKey: key,
@@ -6297,6 +6300,7 @@ describe('useQuery', () => {
await sleep(5)
return { numbers: { current: { id } } }
}
+
function Test() {
const [id, setId] = React.useState(1)
@@ -6357,6 +6361,7 @@ describe('useQuery', () => {
await sleep(5)
return { numbers: { current: { id } } }
}
+
function Test() {
const [id, setId] = React.useState(1)
@@ -6854,10 +6859,12 @@ describe('useQuery', () => {
it('should console.error when there is no queryFn', () => {
const consoleErrorMock = vi.spyOn(console, 'error')
const key = queryKey()
+
function Example() {
useQuery({ queryKey: key })
return <>>
}
+
renderWithClient(queryClient, )
expect(consoleErrorMock).toHaveBeenCalledTimes(1)
@@ -6867,4 +6874,183 @@ describe('useQuery', () => {
consoleErrorMock.mockRestore()
})
+
+ it('should retry on mount when throwOnError returns false', async () => {
+ const key = queryKey()
+ let fetchCount = 0
+ const queryFn = vi.fn().mockImplementation(() => {
+ fetchCount++
+ console.log(`Fetching... (attempt ${fetchCount})`)
+ return Promise.reject(new Error('Simulated 500 error'))
+ })
+
+ function Component() {
+ const { status, error } = useQuery({
+ queryKey: key,
+ queryFn,
+ throwOnError: () => false,
+ retryOnMount: true,
+ staleTime: Infinity,
+ retry: false,
+ })
+
+ return (
+
+
{status}
+ {error &&
{error.message}
}
+
+ )
+ }
+
+ const { unmount, getByTestId } = renderWithClient(
+ queryClient,
+ ,
+ )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+ expect(getByTestId('error')).toHaveTextContent('Simulated 500 error')
+ expect(fetchCount).toBe(1)
+
+ unmount()
+
+ const initialFetchCount = fetchCount
+
+ renderWithClient(queryClient, )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+
+ expect(fetchCount).toBe(initialFetchCount + 1)
+ expect(queryFn).toHaveBeenCalledTimes(2)
+ })
+
+ it('should not retry on mount when throwOnError function returns true', async () => {
+ const key = queryKey()
+ let fetchCount = 0
+ const queryFn = vi.fn().mockImplementation(() => {
+ fetchCount++
+ console.log(`Fetching... (attempt ${fetchCount})`)
+ return Promise.reject(new Error('Simulated 500 error'))
+ })
+
+ function Component() {
+ const { status, error } = useQuery({
+ queryKey: key,
+ queryFn,
+ throwOnError: () => true,
+ retryOnMount: true,
+ staleTime: Infinity,
+ retry: false,
+ })
+
+ return (
+
+
{status}
+ {error &&
{error.message}
}
+
+ )
+ }
+
+ const { unmount, getByTestId } = renderWithClient(
+ queryClient,
+ (
+
+
error
+
{error?.message}
+
+ )}
+ >
+
+ ,
+ )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+ expect(getByTestId('error')).toHaveTextContent('Simulated 500 error')
+ expect(fetchCount).toBe(1)
+
+ unmount()
+
+ const initialFetchCount = fetchCount
+
+ renderWithClient(
+ queryClient,
+ (
+
+
error
+
{error?.message}
+
+ )}
+ >
+
+ ,
+ )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+
+ // Should not retry because throwOnError returns true
+ expect(fetchCount).toBe(initialFetchCount)
+ expect(queryFn).toHaveBeenCalledTimes(1)
+ })
+
+ it('should handle throwOnError function based on actual error state', async () => {
+ const key = queryKey()
+ let fetchCount = 0
+ const queryFn = vi.fn().mockImplementation(() => {
+ fetchCount++
+ console.log(`Fetching... (attempt ${fetchCount})`)
+ return Promise.reject(new Error('Simulated 500 error'))
+ })
+
+ function Component() {
+ const { status, error } = useQuery({
+ queryKey: key,
+ queryFn,
+ throwOnError: (error) => error.message.includes('404'),
+ retryOnMount: true,
+ staleTime: Infinity,
+ retry: false,
+ })
+
+ return (
+
+
{status}
+ {error &&
{error.message}
}
+
+ )
+ }
+
+ const { unmount, getByTestId } = renderWithClient(
+ queryClient,
+ ,
+ )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+ expect(getByTestId('error')).toHaveTextContent('Simulated 500 error')
+ expect(fetchCount).toBe(1)
+
+ unmount()
+
+ const initialFetchCount = fetchCount
+
+ renderWithClient(queryClient, )
+
+ await vi.waitFor(() =>
+ expect(getByTestId('status')).toHaveTextContent('error'),
+ )
+
+ // Should retry because throwOnError returns false (500 error doesn't include '404')
+ expect(fetchCount).toBe(initialFetchCount + 1)
+ expect(queryFn).toHaveBeenCalledTimes(2)
+ })
})
diff --git a/packages/react-query/src/errorBoundaryUtils.ts b/packages/react-query/src/errorBoundaryUtils.ts
index 28c11c0b10..ccd3993f2e 100644
--- a/packages/react-query/src/errorBoundaryUtils.ts
+++ b/packages/react-query/src/errorBoundaryUtils.ts
@@ -25,16 +25,24 @@ export const ensurePreventErrorBoundaryRetry = <
TQueryKey
>,
errorResetBoundary: QueryErrorResetBoundaryValue,
+ query?: Query,
) => {
- if (
- options.suspense ||
- options.throwOnError ||
- options.experimental_prefetchInRender
- ) {
+ if (options.suspense || options.experimental_prefetchInRender) {
// Prevent retrying failed query if the error boundary has not been reset yet
if (!errorResetBoundary.isReset()) {
options.retryOnMount = false
}
+ } else if (options.throwOnError && !errorResetBoundary.isReset()) {
+ if (typeof options.throwOnError === 'function') {
+ if (
+ query?.state.error &&
+ shouldThrowError(options.throwOnError, [query.state.error, query])
+ ) {
+ options.retryOnMount = false
+ }
+ } else {
+ options.retryOnMount = false
+ }
}
}
diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts
index 06690b544f..4158c5734f 100644
--- a/packages/react-query/src/useBaseQuery.ts
+++ b/packages/react-query/src/useBaseQuery.ts
@@ -53,6 +53,14 @@ export function useBaseQuery<
const errorResetBoundary = useQueryErrorResetBoundary()
const client = useQueryClient(queryClient)
const defaultedOptions = client.defaultQueryOptions(options)
+ const query = client
+ .getQueryCache()
+ .get<
+ TQueryFnData,
+ TError,
+ TQueryData,
+ TQueryKey
+ >(defaultedOptions.queryHash)
;(client.getDefaultOptions().queries as any)?._experimental_beforeQuery?.(
defaultedOptions,
@@ -72,8 +80,7 @@ export function useBaseQuery<
: 'optimistic'
ensureSuspenseTimers(defaultedOptions)
- ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary)
-
+ ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary, query)
useClearResetErrorBoundary(errorResetBoundary)
// this needs to be invoked before creating the Observer because that can create a cache entry
diff --git a/packages/react-query/src/useQueries.ts b/packages/react-query/src/useQueries.ts
index a736f5cd1d..6eabef4060 100644
--- a/packages/react-query/src/useQueries.ts
+++ b/packages/react-query/src/useQueries.ts
@@ -242,9 +242,10 @@ export function useQueries<
[queries, client, isRestoring],
)
- defaultedQueries.forEach((query) => {
- ensureSuspenseTimers(query)
- ensurePreventErrorBoundaryRetry(query, errorResetBoundary)
+ defaultedQueries.forEach((queryOptions) => {
+ ensureSuspenseTimers(queryOptions)
+ const query = client.getQueryCache().get(queryOptions.queryHash)
+ ensurePreventErrorBoundaryRetry(queryOptions, errorResetBoundary, query)
})
useClearResetErrorBoundary(errorResetBoundary)