Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom useQuery typing #260

Closed
voxivoid opened this issue Oct 18, 2022 · 9 comments
Closed

Custom useQuery typing #260

voxivoid opened this issue Oct 18, 2022 · 9 comments

Comments

@voxivoid
Copy link
Contributor

voxivoid commented Oct 18, 2022

Hello 馃憢

I'm trying to build some reusable useQuery functions so that I don't have to declare them every time I want to use them. To do so, and to avoid having a lot of type repetition I've also created some custom typing so that I don't need to declare the TError type all the time.

To build my custom typing, I just tried to replicate the usage of types of useQuery in this codebase. It is defined as if follows:

import {
 UseQueryOptions,
 UseQueryReturnType,
} from "@tanstack/vue-query"
import { AxiosError } from "axios"

import { ErrorAPIResponse } from "@/types/model/APIResponse"

export type CustomUseQueryOptions<
 TArgs,
 TData,
 TError = Error | AxiosError<ErrorAPIResponse>,
> = Omit<
 UseQueryOptions<
   TData,
   TError,
   TData,
   TArgs extends void ? string[] : (string | TArgs)[]
 >,
 "queryKey" | "queryFn"
>

export type CustomQueryHook<
 TArgs,
 TData,
 TError = Error | AxiosError<ErrorAPIResponse>,
> = TArgs extends void
 ? (
     options?: CustomUseQueryOptions<TArgs, TData, TError>,
   ) => UseQueryReturnType<TData, TError>
 : (
     args: TArgs,
     options?: CustomUseQueryOptions<TArgs, TData, TError>,
   ) => UseQueryReturnType<TData, TError>

and one example of its usage in a custom useQuery:

import { useContext } from "@nuxtjs/composition-api"
import { useQuery } from "@tanstack/vue-query"

import { ApiPaths } from "@/enums"
import { SuccessAPIResponse } from "@/types/model/APIResponse"
import { User } from "@/types/model/User"
import { CustomQueryHook } from "@/types/vue-query"

type ApiResponse = SuccessAPIResponse<User[]>
export type UsersQueryResponse = User[]

export const queryKey = "UsersQuery"

export const useUsersQuery: CustomQueryHook<void, UsersQueryResponse> = (
  options,
) => {
  const { $axios } = useContext()

  return useQuery(
    [queryKey],
    async () => {
      const { data } = await $axios.get<ApiResponse>(ApiPaths.GET_USERS)

      return data.data
    },
    options,
  )
}

This approach is working with vue-query@1.25.0 but failing with @tanstack/vue-query@4.12.0, causing the following error:

Type '(options: CustomUseQueryOptions<void, UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>> | undefined) => UseQueryReturnType<...>' is not assignable to type '(options?: CustomUseQueryOptions<void, UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>> | undefined) => UseQueryReturnType<...>'.
  Type 'UseQueryReturnType<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>>' is not assignable to type 'UseQueryReturnType<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>, QueryObserverResult<UsersQueryResponse, Error | AxiosError<...>>>'.
    Type 'UseQueryReturnType<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>>' is not assignable to type 'ToRefs<Readonly<QueryObserverLoadingResult<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>>>> & { ...; }'.
      Type 'UseQueryReturnType<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>>' is not assignable to type 'ToRefs<Readonly<QueryObserverLoadingResult<UsersQueryResponse, Error | AxiosError<ErrorAPIResponse>>>>'.
        Types of property 'data' are incompatible.
          Type 'Ref<undefined> | Ref<UsersQueryResponse>' is not assignable to type 'Ref<undefined>'.
            Type 'Ref<UsersQueryResponse>' is not assignable to type 'Ref<undefined>'.
              Type 'UsersQueryResponse' is not assignable to type 'undefined'.ts(2322)

The issue seems to be around the QueryObserverResult generic types, but I couldn't find a reason for it to not work. Am I missing something in my custom type definition or is it an issue with the types themselves?

Thanks!

@Alexis2004
Copy link

Hello!

I also faced the same problem in my project.

The only way I've been able to get around this problem is to use an explicit cast on the result of calling the useQuery function:

export function useProduct(): UseQueryReturnType<ProductDTO, unknown> {
  const options: UseQueryOptions<ProductDTO, unknown> = {
    queryKey: [...],
    queryFn: () => ...
  };

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  return useQuery(options) as UseQueryReturnType<ProductDTO, unknown>;
}

In runtime, this code works properly, that is, the problem is precisely in the type declarations.

By the way, the problem is reproduced not only on the latest version of vue-query@2.0.0-beta.12, but also on his @tanstack/vue-query@4.14.1.

@DamianOsipiuk
Copy link
Owner

I would assume that this is due to additional overloads, that were added to accommodate for initialData provided as an option
https://github.com/DamianOsipiuk/vue-query/pull/240/files
In that case result of initialData is inferred as TQueryFnData. So unless you add the same overloads, you might have a problem with typing it properly.
But this also be something else. However i do not remember other changes that might affect it.

Ideally your custom hooks should not allow for all the options possible, but only those which might be needed to be changed for this particular query.

@DamianOsipiuk
Copy link
Owner

You might want to check the following release, which might fix your problem: https://github.com/TanStack/query/releases/tag/v4.18.1

@Alexis2004
Copy link

The last update of the library allowed me to get rid of the ugly type casts and ts-ignore declarations in all places in the code. But in one place there is a small problem still remains. The difference in this place is that the initialData field is included in the options there:

  const queryClient = useQueryClient();
  const request = ...;

  const options: UseQueryOptions<ProductDTO, unknown> = {
    queryKey: [PRODUCT_CACHE_KEY, request],
    queryFn: () => loadProduct(request.value),
    initialData: () => findProductInCatalogCache(request.value, queryClient),
  };

  return useQuery(options);
async loadProduct(request: LoadProductRequest): Promise<ProductDTO> {
    return ...
}
function findProductInCatalogCache(
  loadProductRequest: LoadProductRequest | undefined,
  queryClient: QueryClient,
): ProductDTO | undefined {
  return ...;
}

If I remove explicit type declaration from options variable I receive error:

TS2322: Type 'UseQueryDefinedReturnType<ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined, unknown>' is not assignable to type 'UseQueryReturnType<ProductDTO, unknown>'.
 聽聽Type 'Omit<ToRefs<Readonly<QueryObserverRefetchErrorResult<ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined, unknown>>>, "refetch" | "remove"> & { ...; }' is not assignable to type 'UseQueryReturnType<ProductDTO, unknown>'.
  聽聽聽聽Type 'Omit<ToRefs<Readonly<QueryObserverRefetchErrorResult<ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined, unknown>>>, "refetch" | "remove"> & { ...; }' is not assignable to type 'Omit<ToRefs<Readonly<QueryObserverLoadingResult<ProductDTO, unknown>>> & { suspense: () => Promise<QueryObserverResult<ProductDTO, unknown>>; }, "refetch" | "remove"> & { ...; }'.
   聽聽聽聽聽聽Type 'Omit<ToRefs<Readonly<QueryObserverRefetchErrorResult<ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined, unknown>>>, "refetch" | "remove"> & { ...; }' is not assignable to type 'Omit<ToRefs<Readonly<QueryObserverLoadingResult<ProductDTO, unknown>>> & { suspense: () => Promise<QueryObserverResult<ProductDTO, unknown>>; }, "refetch" | "remove">'. 聽聽聽聽聽聽聽聽Types of property 'data' are incompatible.
    聽聽聽聽聽聽聽聽聽聽Type 'Ref<ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined>' is not assignable to type 'Ref<undefined>'.
     聽聽聽聽聽聽聽聽聽聽聽聽Type 'ProductDTO | Ref<undefined> | Ref<ProductDTO> | undefined' is not assignable to type 'undefined'.
      聽聽聽聽聽聽聽聽聽聽聽聽聽聽Type 'ProductDTO' is not assignable to type 'undefined'.

@DamianOsipiuk
Copy link
Owner

Do you by any chance have a type annotation somewhere around this code as UseQueryReturnType<ProductDTO, unknown>?
Because this type would not be correct here, since your initialData can still return undefined.

@Alexis2004
Copy link

@DamianOsipiuk, I prepared a repo with a minimal reproduction of the problem I wrote about above.

Repo Link: https://github.com/Alexis2004/vue-query-typings-issue
Link to Stackblitz: https://stackblitz.com/~/github.com/Alexis2004/vue-query-typings-issue

Everything in this example works as it should, except for the fact that I have to explicitly type the options variable (see src/product.ts:49).
If I remove the type definition from here, then I cannot find the correct return type for the useProduct function.

@DamianOsipiuk
Copy link
Owner

@Alexis2004 I have looked at your example, basically when you use initialData you should use UseQueryDefinedReturnType instead of UseQueryReturnType.

@Alexis2004
Copy link

@DamianOsipiuk, thanks for the advice!
Indeed, everything works as it should, if I specify the type UseQueryDefinedReturnType.

@DamianOsipiuk
Copy link
Owner

Glad that it worked! 馃帀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants