I am attempting to wrap useQuery to centralize my use of it. However, the issue I am running into is the queryFn is built at runtime, so when wrapped in my custom hook, I have to conditionally return the hook's promise based on whether the queryFn is ready or not. This breaks the rules of hooks. Does anyone have information on how to properly wrap the useQuery in my custom hook? Code in it's current state is below. The main bit to look at is the return and how queryFn is being set. That's the crux of the issue.
import {
QueryFunction,
QueryKey,
UseQueryOptions,
UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import {
ApiQueryConfig,
QueryPathParamsType,
QueryReturnType,
QueryUrlParamsType,
useApiClient,
} from "@api";
import { combineQueryKey } from "./utils";
import { useEffect, useState } from "react";
const useApiQuery = <
T extends ApiQueryConfig<any, Record<string, string>, Record<string, any>>,
ReturnType extends QueryReturnType<T>,
PathParamsType extends QueryPathParamsType<T>,
UrlParamsType extends QueryUrlParamsType<T>
>(
apiQueryConfig: ApiQueryConfig<ReturnType, PathParamsType, UrlParamsType>,
pathParams?: PathParamsType,
urlParams?: UrlParamsType,
axiosRequestConfig?: AxiosRequestConfig,
tanstackConfig?: UseQueryOptions<
AxiosResponse<ReturnType>,
Error,
AxiosResponse<ReturnType>,
QueryKey
>
): UseQueryResult<AxiosResponse<ReturnType, any>, Error> => {
const apiClient = useApiClient();
const [queryFn, setQueryFn] = useState<
QueryFunction<AxiosResponse<ReturnType, any>> | undefined
>(undefined);
const axiosConfigNonOverridable = {
params: urlParams || {},
};
const axiosConfigOverridable: AxiosRequestConfig = {
timeout: 10 * 1000,
};
const mergedAxiosRequestConfig: AxiosRequestConfig = {
...axiosConfigOverridable,
...(axiosRequestConfig || {}),
...axiosConfigNonOverridable,
};
const tanstackConfigNonOverridable: typeof tanstackConfig = {
enabled: !!apiClient && (tanstackConfig?.enabled || true),
};
const tanstackConfigOverridable: typeof tanstackConfig = {
networkMode: "online",
retry: 2,
retryOnMount: true,
staleTime: Infinity,
cacheTime: 10 * 60 * 1000,
refetchOnMount: true,
refetchOnWindowFocus: false,
refetchOnReconnect: true,
};
const mergedTanstackConfig: typeof tanstackConfig = {
...tanstackConfigOverridable,
...(tanstackConfig || {}),
...tanstackConfigNonOverridable,
};
const path = pathParams
? Object.entries(pathParams).reduce(
(accPath, [key, value]) => accPath.replace(`{${key}}`, value),
apiQueryConfig.apiPath
)
: apiQueryConfig.apiPath;
const queryKey = combineQueryKey(
apiQueryConfig.queryKey.baseQueryKey,
{ ...pathParams, ...urlParams },
apiQueryConfig.queryKey.dynamicQueryKey
);
useEffect(() => {
if (apiClient) {
console.log(apiClient);
setQueryFn(() => apiClient!.get(path, mergedAxiosRequestConfig));
}
// We should not use exhaustive deps here. Deps should be intentional.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [apiClient]);
if (!queryFn) {
return { isLoading: true } as UseQueryResult<
AxiosResponse<ReturnType, any>,
Error
>;
}
return useQuery<AxiosResponse<ReturnType>, Error>(
queryKey,
queryFn!,
mergedTanstackConfig
);
};
export { useApiQuery };