query icon indicating copy to clipboard operation
query copied to clipboard

An infinite refetch occurs when an error response is received when using useQueries.

Open doumAb180 opened this issue 3 years ago • 3 comments

Describe the bug

An infinite refetch occurs when an error response is received when using useQueries. However, when remove the suspense query option, an infinite refresh does not occur. And it is a situation where the Errorboundary cannot catch the error. It can be reproduced by correcting the request url to cause an error in the link below.

Your minimal, reproducible example

https://codesandbox.io/s/angry-diffie-rekf90?file=/src/query.ts:278-577

Steps to reproduce

  1. Go to example.
  2. Modify the request url.
  3. So you can see the infinite refetching.

Expected behavior

When using useQueries, I want the retry and suspense options to work properly and I hope the ErrorBoundary can catch errors well.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

mac os chrome 108.0.5359.98

Tanstack Query version

v4.19.1

TypeScript version

v4.9.4

Additional context

No response

doumAb180 avatar Dec 15 '22 04:12 doumAb180

interesting, thanks for reporting. I wonder why the same doesn't happen for useQuery. I think what's happening is:

  • query mounts, fetches, suspends
  • query errors, but we don't throw to the error boundary, so query re-mounts
  • erroneous queries are always considered stale, and since a component mounts, we fetch and suspend again.

There must be something in useQuery to prevent this that we haven't added to useQueries. I think it has something to do with retryOnMount, but we should set that to false in both cases:

https://github.com/TanStack/query/blob/34ca5581217dae4133046f6cfbc746a22be14dd9/packages/react-query/src/errorBoundaryUtils.ts#L27-L34

TkDodo avatar Dec 15 '22 07:12 TkDodo

@TkDodo Thank you for your quick reply.

I tried many things by referring to your answer, but when I modified the retryOnMount to false, it worked correctly. The problem seems to be that if the retry option is set to false, retry will not occur when the observer is mounted.

Anyway, your answer was very helpful. Thank you 🙇‍♂️ 😃

doumAb180 avatar Dec 15 '22 12:12 doumAb180

yes, this is exactly what the retryOnMount flag is for. From the docs:

retryOnMount: boolean If set to false, the query will not be retried on mount if it contains an error. Defaults to true.

I still think you, as a user, should not need to set that flag to false for yourself when using suspense. It works for useQuery, so it should also work for useQueries.

TkDodo avatar Dec 15 '22 12:12 TkDodo

I have prepared a failing unit test for this scenario https://github.com/TanStack/query/pull/4882

chekrd avatar Jan 26 '23 13:01 chekrd

I already created a wrapper around the useQueries function for better handling types and queries. By adding retryOnMount: errorResetBoundary.isReset() the infinite loading problem the problem looks fixed and the reset function on the ErrorBoundary does indeed retry the query. Important: I use suspense: true by default.

/** @version 1.0.0 */
export function useQueries<Qs extends any[]>({ queries }: { queries: readonly [...{ [Key in keyof Qs]: Qs[Key] & GetOptions<Qs[Key]> }] }): QueryResults<Qs>
{
	const loads = React.useMemo(() => queries.map(options => Query.prepare(options.queryFn)), [])
	const errorResetBoundary = useQueryErrorResetBoundary() // <- get the error boundary QueryErrorResetBoundaryValue

	return useQueriesBase({
		// @ts-ignore
		queries: queries.map(
			(options, index) =>
			{
				const queryKey = options.queryKey
				const enabled = areValidArgs(queryKey)
				const load = loads[index]

				return {
					...Query.Options.get(options.queryFn),
					retryOnMount: errorResetBoundary.isReset(), // <- this line here
					...options,
					queryFn: enabled ? load : undefined,
					enabled,
					queryKey: enabled ? queryKey : undefined,
				}
			}
		),
	}) as QueryResults<Qs>
}

So a quick fix for users who use suspense: true could be:

import { useQueries as useQueriesBase, useQueryErrorResetBoundary } from "@tanstack/react-query"

export const useQueries: typeof useQueriesBase = options =>
{
	const errorResetBoundary = useQueryErrorResetBoundary()
	return useQueriesBase({
		...options,
		queries: options.queries.map(query => ({ retryOnMount: errorResetBoundary.isReset(), ...query })),
	})
}

projectnatz avatar Mar 01 '23 09:03 projectnatz

if someone wants to tackle this one, please do. The failing test case provided by @chekrd should be helpful:

  • #4882

TkDodo avatar Mar 05 '23 16:03 TkDodo

@TkDodo I gave this a shot in the dark. There were some clear differences with the order of operations between useQuery and useQueries.

More info in the PR. Let me know your thoughts!

bouncehead13 avatar Apr 14 '23 00:04 bouncehead13