diff --git a/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts b/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts index 8e0656bf9..1faa35bed 100644 --- a/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts +++ b/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts @@ -3,6 +3,7 @@ import { act } from 'react-dom/test-utils'; import { renderHook, waitFor } from '@/test-utils/rtl'; import useMergedInfiniteQueries from '../use-merged-infinite-queries'; +import { UseMergedInfiniteQueriesError } from '../use-merged-infinite-queries-error'; import { type SingleInfiniteQueryOptions } from '../use-merged-infinite-queries.types'; type MockAPIResponse = { @@ -105,6 +106,7 @@ describe(useMergedInfiniteQueries.name, () => { const [mergedResult] = result.current; expect(mergedResult.data).toStrictEqual([0, 2, 4, 6, 8]); expect(mergedResult.status).toStrictEqual('error'); + expect(mergedResult.error).toBeInstanceOf(UseMergedInfiniteQueriesError); }); }); diff --git a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries-error.ts b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries-error.ts new file mode 100644 index 000000000..d9cdcc431 --- /dev/null +++ b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries-error.ts @@ -0,0 +1,8 @@ +export class UseMergedInfiniteQueriesError extends Error { + errors: Array; + constructor(message: string, errors: Array, options?: ErrorOptions) { + super(message, options); + this.errors = errors; + this.name = 'UseMergedInfiniteQueriesError'; + } +} diff --git a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts index d710d0252..1ac896643 100644 --- a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts +++ b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts @@ -1,4 +1,4 @@ -import { useMemo, useState, useEffect } from 'react'; +import { useMemo, useState, useEffect, useCallback } from 'react'; import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query'; @@ -6,6 +6,7 @@ import mergeSortedArrays from '@/utils/merge-sorted-arrays'; import getMergedFetchNextPage from './helpers/get-merged-fetch-next-page'; import getMergedQueryStatus from './helpers/get-merged-query-status'; +import { UseMergedInfiniteQueriesError } from './use-merged-infinite-queries-error'; import { type SingleInfiniteQueryResult, type MergedQueriesResults, @@ -26,8 +27,8 @@ import { * @param flattenResult - A function that takes the expected query result and flattens it into an array of items * @param compare - A comparison function used to sort and merge results. * The function should accept two arguments and return: - * - A number > 0 if the first argument has a higher priority. - * - A number <= 0 if the second argument has a higher or equal priority. + * - A number > 0 if the second argument comes first. + * - A number <= 0 if the first argument comes first. * - **Note:** The comparison logic must match the sorting logic used in the queries to maintain consistency. * * @returns A tuple [mergedQueryResults, queryResults]: @@ -85,6 +86,14 @@ export default function useMergedInfiniteQueries({ }); }, [flattenedDataArrays, count, compare]); + const refetchQueriesWithError = useCallback(() => { + queryResults.forEach((res) => { + if (res.isError) { + res.refetch(); + } + }); + }, [queryResults]); + const mergedQueryResults = { data: sortedArray, status: getMergedQueryStatus(queryResults), @@ -99,6 +108,18 @@ export default function useMergedInfiniteQueries({ pageSize, setCount, }), + error: queryResults.some((qr) => qr.isError) + ? new UseMergedInfiniteQueriesError( + 'One or more infinite queries failed', + queryResults.reduce((errors: Array, qr) => { + if (qr.isError) { + errors.push(qr.error); + } + return errors; + }, []) + ) + : null, + refetch: refetchQueriesWithError, // ...add other properties if needed }; return [mergedQueryResults, queryResults]; diff --git a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts index 68fa193d0..bd711b6c7 100644 --- a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts +++ b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts @@ -5,6 +5,8 @@ import { type useInfiniteQuery, } from '@tanstack/react-query'; +import { type UseMergedInfiniteQueriesError } from './use-merged-infinite-queries-error'; + export type MergedQueryStatus = 'idle' | 'loading' | 'success' | 'error'; export type MergedQueriesResults = { @@ -15,6 +17,8 @@ export type MergedQueriesResults = { isFetchingNextPage: boolean; hasNextPage: boolean; fetchNextPage: () => void; + error: UseMergedInfiniteQueriesError | null; + refetch: () => void; }; export type SingleInfiniteQueryOptions = diff --git a/src/utils/request/request-error.ts b/src/utils/request/request-error.ts index 5db5ea521..94e2648a1 100644 --- a/src/utils/request/request-error.ts +++ b/src/utils/request/request-error.ts @@ -1,8 +1,19 @@ +import { type ZodIssue } from 'zod'; + export class RequestError extends Error { status: number; - constructor(message: string, status: number, options?: ErrorOptions) { + validationErrors: Array | undefined; + constructor( + message: string, + status: number, + validationErrors?: Array, + options?: ErrorOptions + ) { super(message, options); this.status = status; + if (validationErrors?.length) { + this.validationErrors = validationErrors; + } this.name = 'RequestError'; } } diff --git a/src/utils/request/request.ts b/src/utils/request/request.ts index e90fe7c8a..f7a8ece2f 100644 --- a/src/utils/request/request.ts +++ b/src/utils/request/request.ts @@ -14,9 +14,14 @@ export default function request( async (res) => { if (!res.ok) { const error = await res.json(); - throw new RequestError(error.message, res.status, { - cause: error.cause, - }); + throw new RequestError( + error.message, + res.status, + error.validationErrors, + { + cause: error.cause, + } + ); } return res; }