import { type QueryKey, type QueryObserverBaseResult, type UseQueryOptions, useQuery, useQueryClient } from '@tanstack/react-query'; import { equals, has, includes, isNil, not, omit } from 'ramda'; import { useEffect, useMemo, useRef } from 'react'; import type { JsonDecoder } from 'ts.data.json'; import useSnackbar from '../../Snackbar/useSnackbar'; import { useDeepCompare } from '../../utils'; import { type CatchErrorProps, customFetch, type ResponseError } from '../customFetch'; import { errorLog } from '../logger'; export interface UseFetchQueryProps { baseEndpoint?: string; catchError?: (props: CatchErrorProps) => void; decoder?: JsonDecoder.Decoder; defaultFailureMessage?: string; doNotCancelCallsOnUnmount?: boolean; fetchHeaders?: HeadersInit; getEndpoint: (params?: PrefetchEndpointParams) => string; getQueryKey: () => QueryKey; httpCodesBypassErrorSnackbar?: Array; isPaginated?: boolean; queryOptions?: { suspense?: boolean; } & Omit< UseQueryOptions, 'queryKey' | 'queryFn' >; useLongCache?: boolean; } export interface PrefetchQueryParams { endpointParams?: PrefetchEndpointParams; queryKey: QueryKey; } export interface PrefetchPageParams { getPrefetchQueryKey: (page: number) => QueryKey; page: number; } export type UseFetchQueryState = { data?: T; error: Omit | null; fetchQuery: () => Promise; prefetchNextPage: ({ page, getPrefetchQueryKey }: PrefetchPageParams) => void; prefetchPreviousPage: ({ page, getPrefetchQueryKey }: PrefetchPageParams) => void; prefetchQuery: ({ endpointParams, queryKey }: PrefetchQueryParams) => void; } & Omit; export interface PrefetchEndpointParams { page: number; } const useFetchQuery = ({ getEndpoint, getQueryKey, catchError, decoder, defaultFailureMessage, fetchHeaders, isPaginated, queryOptions, httpCodesBypassErrorSnackbar = [], baseEndpoint, doNotCancelCallsOnUnmount = false, useLongCache }: UseFetchQueryProps): UseFetchQueryState => { const dataRef = useRef(undefined); const { showErrorMessage } = useSnackbar(); const isCypressTest = equals( (window as Window & { Cypress?: { testingType: string } }).Cypress ?.testingType, 'component' ); const cacheOptions = !isCypressTest && useLongCache ? { gcTime: 60 * 1000 } : {}; const queryData = useQuery({ queryFn: ({ signal }): Promise => customFetch({ baseEndpoint, catchError, decoder, defaultFailureMessage, endpoint: getEndpoint(), headers: new Headers(fetchHeaders), signal }), queryKey: getQueryKey(), ...cacheOptions, ...queryOptions }); const queryClient = useQueryClient(); const manageError = (): void => { const data = queryData.data as ResponseError | undefined; if (data?.isError) { errorLog(data.message); const hasACorrespondingHttpCode = includes( data?.statusCode || 0, httpCodesBypassErrorSnackbar ); if (!hasACorrespondingHttpCode) { showErrorMessage(data?.message); } } }; const prefetchQuery = ({ endpointParams, queryKey }: PrefetchQueryParams): void => { queryClient.prefetchQuery({ queryFn: ({ signal }): Promise => customFetch({ baseEndpoint, catchError, decoder, defaultFailureMessage, endpoint: getEndpoint(endpointParams), headers: new Headers(fetchHeaders), signal }), queryKey }); }; const prefetchNextPage = ({ page, getPrefetchQueryKey }: PrefetchPageParams): void => { if (!isPaginated) { return; } const nextPage = page + 1; prefetchQuery({ endpointParams: { page: nextPage }, queryKey: getPrefetchQueryKey(nextPage) }); }; const prefetchPreviousPage = ({ page, getPrefetchQueryKey }: PrefetchPageParams): void => { if (!isPaginated) { return; } const previousPage = page - 1; prefetchQuery({ endpointParams: { page: previousPage }, queryKey: getPrefetchQueryKey(previousPage) }); }; const fetchQuery = (): Promise => { return queryClient.fetchQuery({ queryFn: ({ signal }): Promise => customFetch({ baseEndpoint, catchError, decoder, defaultFailureMessage, endpoint: getEndpoint(), headers: new Headers(fetchHeaders), signal }), queryKey: getQueryKey() }); }; const data = useMemo( () => not(has('isError', queryData.data)) ? (queryData.data as T) : undefined, [queryData.data] ); if (!isNil(data)) { dataRef.current = data; } const errorData = queryData.data as ResponseError | undefined; useEffect(() => { return (): void => { if (doNotCancelCallsOnUnmount) { return; } queryClient.cancelQueries({ queryKey: getQueryKey() }); }; }, [doNotCancelCallsOnUnmount]); useEffect( () => { manageError(); }, useDeepCompare([queryData.data]) ); return { ...omit(['data', 'error'], queryData), data: dataRef.current, error: errorData?.isError ? omit(['isError'], errorData) : null, fetchQuery, prefetchNextPage, prefetchPreviousPage, prefetchQuery }; }; export default useFetchQuery;