-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Error boundary handling for useQueries #4177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d4bf025
5d7d9ef
3278944
79fe68c
237912b
7f81e88
060de7a
c92a897
3fd6426
3518f3c
096c82a
e767221
a48f3f4
e5ab278
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import type { | ||
DefaultedQueryObserverOptions, | ||
Query, | ||
QueryKey, | ||
QueryObserverResult, | ||
UseErrorBoundary, | ||
} from '@tanstack/query-core' | ||
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary' | ||
import * as React from 'react' | ||
import { shouldThrowError } from './utils' | ||
|
||
export const ensurePreventErrorBoundaryRetry = < | ||
TQueryFnData, | ||
TError, | ||
TData, | ||
TQueryData, | ||
TQueryKey extends QueryKey, | ||
>( | ||
options: DefaultedQueryObserverOptions< | ||
TQueryFnData, | ||
TError, | ||
TData, | ||
TQueryData, | ||
TQueryKey | ||
>, | ||
errorResetBoundary: QueryErrorResetBoundaryValue, | ||
) => { | ||
if (options.suspense || options.useErrorBoundary) { | ||
// Prevent retrying failed query if the error boundary has not been reset yet | ||
if (!errorResetBoundary.isReset()) { | ||
options.retryOnMount = false | ||
} | ||
} | ||
} | ||
|
||
export const useClearResetErrorBoundary = ( | ||
errorResetBoundary: QueryErrorResetBoundaryValue, | ||
) => { | ||
React.useEffect(() => { | ||
errorResetBoundary.clearReset() | ||
}, [errorResetBoundary]) | ||
} | ||
|
||
export const getHasError = < | ||
TData, | ||
TError, | ||
TQueryFnData, | ||
TQueryData, | ||
TQueryKey extends QueryKey, | ||
>({ | ||
result, | ||
errorResetBoundary, | ||
useErrorBoundary, | ||
query, | ||
}: { | ||
result: QueryObserverResult<TData, TError> | ||
errorResetBoundary: QueryErrorResetBoundaryValue | ||
useErrorBoundary: UseErrorBoundary< | ||
TQueryFnData, | ||
TError, | ||
TQueryData, | ||
TQueryKey | ||
> | ||
query: Query<TQueryFnData, TError, TQueryData, TQueryKey> | ||
}) => { | ||
return ( | ||
result.isError && | ||
!errorResetBoundary.isReset() && | ||
!result.isFetching && | ||
shouldThrowError(useErrorBoundary, [result.error, query]) | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,12 @@ import { notifyManager, QueriesObserver } from '@tanstack/query-core' | |
import { useQueryClient } from './QueryClientProvider' | ||
import type { UseQueryOptions, UseQueryResult } from './types' | ||
import { useIsRestoring } from './isRestoring' | ||
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary' | ||
import { | ||
ensurePreventErrorBoundaryRetry, | ||
getHasError, | ||
useClearResetErrorBoundary, | ||
} from './errorBoundaryUtils' | ||
|
||
// This defines the `UseQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`. | ||
// - `context` is omitted as it is passed as a root-level option to `useQueries` instead. | ||
|
@@ -184,5 +190,26 @@ export function useQueries<T extends any[]>({ | |
observer.setQueries(defaultedQueries, { listeners: false }) | ||
}, [defaultedQueries, observer]) | ||
|
||
const errorResetBoundary = useQueryErrorResetBoundary() | ||
|
||
defaultedQueries.forEach((query) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we rename this from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry, I meant more globally that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay nevermind, this is actually a bigger refactoring. we have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EDIT:
ORIGINAL COMMENT BELOW: Let me explain how I understand it. First, here are 3 facts about the code in this PR its current state:
export function useQueries<T extends any[]>({
queries,
context,
}: {
queries: readonly [...QueriesOptions<T>]
context?: UseQueryOptions['context']
})
const defaultedQueries = React.useMemo(
() =>
queries.map((options) => {
const defaultedOptions = queryClient.defaultQueryOptions(options)
// Make sure the results are already in fetching state before subscribing or updating options
defaultedOptions._optimisticResults = isRestoring
? 'isRestoring'
: 'optimistic'
return defaultedOptions
}),
[queries, queryClient, isRestoring],
) It would be strange if mapping with default values would suddenly convert const [observer] = React.useState(
() => new QueriesObserver(queryClient, defaultedQueries),
) So here are my thoughts to sum it up:
WDYT? If that makes sense, maybe we could also do this to make it even more consistent: export function useQueries<T extends any[]>({
queries: queriesOptions,
context,
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah the naming is weird, I agree. But we have a public method called so for now, let's:
there are also conflicts now, sorry. If those are solved we can go ahead and merge 🚀 |
||
ensurePreventErrorBoundaryRetry(query, errorResetBoundary) | ||
}) | ||
|
||
useClearResetErrorBoundary(errorResetBoundary) | ||
|
||
const firstSingleResultWhichShouldThrow = result.find((singleResult, index) => | ||
getHasError({ | ||
result: singleResult, | ||
errorResetBoundary, | ||
useErrorBoundary: defaultedQueries[index]?.useErrorBoundary ?? false, | ||
query: observer.getQueries()[index]!, | ||
}), | ||
) | ||
|
||
if (firstSingleResultWhichShouldThrow?.error) { | ||
throw firstSingleResultWhichShouldThrow.error | ||
} | ||
|
||
return result as QueriesResults<T> | ||
} |
Uh oh!
There was an error while loading. Please reload this page.