I have a common method to use useQuery or useMutation based on the HTTP method as prop. But the return type of this method is containing 'QueryObserverRefetchErrorResult<any, Error>' which doesn't have methods included in useMutation or useQuery.
I tried conditional types such as but the issue was same.
type ResultType<Method extends NTWRK_METHODS> = Method extends NTWRK_METHODS.GET ? NetworkQueryResult : NetworkMutationResult;
My method is
type NetworkQueryResult = UseQueryResult<any, Error>;
type NetworkMutationResult = UseMutationResult<any, Error, void, unknown>;
export function useNetworkQuery(
props: IUseNetworkCallWithClient,
): NetworkQueryResult | NetworkMutationResult {
const {queryKey, axiosClient, url, persist, method, params = ''} = props;
if (method === NTWRK_METHODS.GET) {
return useQuery({
queryKey: [queryKey],
queryFn: async () => {
const data = await axiosClient.request({
method: NTWRK_METHODS.GET,
url: url,
params: params,
});
return data.data;
},
enabled: false,
meta: {persist},
});
} else {
return useMutation({
mutationKey: [queryKey],
mutationFn: async () => {
const data = await axiosClient.request({
method: method,
url: url,
data: params,
});
return data.data;
},
meta: {persist},
});
}
}
I am calling it as
export const makeNetworkCall = (props: IUseNetworkCallWithoutClient) => {
const {method, queryKey, url, persist, params = ''} = props;
const config = {
axiosClient,
queryKey: queryKey,
method: method,
url: url,
persist: persist,
params: params,
};
return useNetworkQuery(config);
};
and then
export const useAuthCalls = () => {
const logIn = () => {
makeNetworkCall({
method: NTWRK_METHODS.POST,
url: '/users',
queryKey: 'login',
persist: true,
params: '',
});
};
return {logIn};
};
Despite what we discussed earlier in the comments I'll give you an answer regarding the typing of your useNetworkQuery
function which is pretty much what you're asking in this post.
What you need here is function overloading to help differentiate what is being returned by useNetworkQuery
.
Simply defining the return type of your function as NetworkQueryResult | NetworkMutationResult
will not work well enough because TypeScript won't be able to tell which of these two is the one returned, only that whatever is returned could be either one of these "so treat them as if they were both".
How about replacing your useNetworkQuery
to this?
export function useNetworkQuery<TData = any, TError extends Error = Error>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: NTWRK_METHODS.GET; }): UseQueryResult<TData, TError>;
export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: Exclude<NTWRK_METHODS, NTWRK_METHODS.GET>; }): UseMutationResult<TData, TError, TVariables>;
export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables> {
const { queryKey, axiosClient, url, persist, method, params = "" } = props;
if (method === NTWRK_METHODS.GET) {
return useQuery<TData, TError>({
queryKey: [queryKey],
queryFn: async () => {
const data = await axiosClient.request({
method: NTWRK_METHODS.GET,
url: url,
params: params,
});
return data.data;
},
enabled: false,
meta: { persist },
});
}
return useMutation<TData, TError, TVariables>({
mutationKey: [queryKey],
mutationFn: async () => {
const data = await axiosClient.request({
method: method,
url: url,
data: params,
});
return data.data;
},
meta: { persist },
});
}
A little explanation regarding this:
UseQueryResult<TData, TError>
if the given props
includes a method
property equal to NTWRK_METHODS.GET
or changes it to UseMutationResult<TData, TError, TVariables>
for any other NTWRK_METHODS
that is not NTWRK_METHODS.GET
.TData
, TError
, and TVariables
which are generics accepted by the useQuery
and useMutation
functions. This way, your useNetworkQuery
function can keep the correct typing behavior instead of making everything be set to any
.The previous function should work well enough, but what about makeNetworkCall
? If you try to return useNetworkQuery
directly you'll notice you'll get an error saying that the implementation call signature is not externally visible. That's because when you overload a function, only the overload signatures are the ones that are taken into account calling the function and the implementation one serves only for the implementation.
You should then add an extra overload to useNetworkQuery
:
export function useNetworkQuery<TData = any, TError extends Error = Error, TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables>;
Which is basically the same signature as the implementation one.
Now, you can overload your makeNetworkCall
function:
export function makeNetworkCall<TData = any, TError extends Error = Error>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: NTWRK_METHODS.GET; }): UseQueryResult<TData, TError>;
export function makeNetworkCall<TData = any, TError extends Error = Error, TVariables = any>(props: Exclude<IUseNetworkCallWithClient, "method"> & { method: Exclude<NTWRK_METHODS, NTWRK_METHODS.GET>; }): UseMutationResult<TData, TError, TVariables>;
export function makeNetworkCall<TData = any,TError extends Error = Error,TVariables = any>(props: IUseNetworkCallWithClient): UseQueryResult<TData, TError> | UseMutationResult<TData, TError, TVariables>;
export function makeNetworkCall(props: IUseNetworkCallWithoutClient) {
const { method, queryKey, url, persist, params = "" } = props;
const config = {
axiosClient,
queryKey: queryKey,
method: method,
url: url,
persist: persist,
params: params,
};
return useNetworkQuery(config);
}
Finally, you can call your makeNetworkCall
hook from your components:
const getQuery = makeNetworkCall({ method: NTWRK_METHODS.GET }); // Type is UseQueryResult.
const postQuery = makeNetworkCall({ method: NTWRK_METHODS.POST }); // Type is UseMutationResult.
As a sidenote regarding the useAuthCalls
hook...
How about replacing it with:
export const useAuthCalls = () => {
const logInMutation = makeNetworkCall({
method: NTWRK_METHODS.POST,
url: "/users",
queryKey: "login",
persist: true,
params: "",
});
return {
logIn: logInMutation.mutateAsync,
};
};
This way, you're always using the makeNetworkCall
hook and only returning the mutateAsync
for the logIn
call. You should be able to call this from your action handlers with no problem.
As a suggestion, rename your makeNetworkCall
to something that starts with use
. The reason for this is to remind you that this is in fact a hook and should be treated as one. Meaning, it should always be called in order and only inside React components.
If it helps you visualize this better, here's a CodeSandbox that you can check out: