Search code examples
typescriptreact-reduxredux-toolkitrtk-query

How to use RTKQuery queryFn with TypeScript


NOTE: This now works fine in RTKQuery v2 as it has been rewritten to TypeScript. Original question below.


Short description

I'm trying to compile below piece of TypeScript code. Tsc isn't satisfied with my return type from the queryFn function. Am I doing something wrong or am I missing something?

The code

import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query/fetchBaseQuery';
import { baseApi } from '../../../api/baseApi';
import { RootState } from '../../../redux/store';
import { UserResponse } from './userApiTypes';

export const extendedApi = baseApi.injectEndpoints({
    endpoints: build => ({
        getUser: build.query<UserResponse, void>({
            queryFn: async (arg, api, extraOptions, baseQuery) => {
                const state = api.getState() as RootState;

                if (!state.auth.loginToken && !state.auth.refreshToken)
                    return { error: { error: `UNAUTHORIZED`, status: `CUSTOM_ERROR` } as FetchBaseQueryError };

                return await baseQuery(`/user/me`);
            },
        }),
    }),
});

export const { useGetUserQuery, useLazyGetUserQuery } = extendedApi;

The error

Type '(arg: void, api: BaseQueryApi, extraOptions: any, baseQuery: (arg: string | FetchArgs) => Promise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>) => Promise<...>' is not assignable to type '(arg: void, api: BaseQueryApi, extraOptions: any, baseQuery: (arg: string | FetchArgs) => Promise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>) => MaybePromise<...>'.
  Type 'Promise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>' is not assignable to type 'MaybePromise<QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>>'.
    Type 'Promise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>' is not assignable to type 'PromiseLike<QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>>'.
      Types of property 'then' are incompatible.
        Type '<TResult1 = QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>, TResult2 = never>(onfulfilled?: ((value: QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>) => TResult1 | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ......' is not assignable to type '<TResult1 = QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>, TResult2 = never>(onfulfilled?: ((value: QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>) => TResult1 | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined...'.
          Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
            Types of parameters 'value' and 'value' are incompatible.
              Type 'QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>' is not assignable to type 'QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>'.
                Type '{ error?: undefined; data: unknown; meta?: FetchBaseQueryMeta | undefined; }' is not assignable to type 'QueryReturnValue<UserResponse, FetchBaseQueryError, unknown>'.
                  Type '{ error?: undefined; data: unknown; meta?: FetchBaseQueryMeta | undefined; }' is not assignable to type '{ error?: undefined; data: UserResponse; meta?: unknown; }'.
                    Types of property 'data' are incompatible.
                      Type 'unknown' is not assignable to type 'UserResponse'.ts(2322)
endpointDefinitions.d.ts(37, 5): The expected type comes from property 'queryFn' which is declared here on type 'Omit<EndpointDefinitionWithQuery<void, (args: string | FetchArgs, api: BaseQueryApi, extraOptions: any) => Promise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>, UserResponse> & { ...; } & { ...; } & QueryExtraOptions<...>, "type"> | Omit<...>'

Bad workaround

Looking at the examples on the internet I am able to compile this code when I remove types from build.query to make this enpoint like this:

getUser: build.query({ // changed here
            queryFn: async (arg, api, extraOptions, baseQuery) => {
                const state = api.getState() as RootState;

                if (!state.auth.loginToken && !state.auth.refreshToken)
                    return { error: { error: `UNAUTHORIZED`, status: `CUSTOM_ERROR` } as FetchBaseQueryError };

                return await baseQuery(`/user/me`);
            },
        }),

But then I lose strong typing everywhere in my code which makes this workaround at least controversial.


Solution

  • I ended up with this code but I'm not sure if that's the recommended approach. There is a lot going on after the baseQuery is called just to satisfy the compiler:

    import { baseApi } from '../../../api/baseApi';
    import { BaseResponse } from '../../../api/baseApiTypes';
    import { RootState } from '../../../redux/store';
    import { UserResponse } from './userApiTypes';
    
    export const extendedApi = baseApi.injectEndpoints({
      endpoints: build => ({
        getUser: build.query<UserResponse, void>({
          queryFn: async (arg, api, extraOptions, baseQuery) => {
            const state = api.getState() as RootState;
    
            if (!state.auth.tokens)
              return { error: { error: `UNAUTHORIZED`, status: `CUSTOM_ERROR` } };
    
            const result = await baseQuery(`/user/me`);
    
            if (result.error)
              return { error: result.error };
    
            const baseResponseWithUser = result.data as BaseResponse<UserResponse>;
    
            if (!baseResponseWithUser.data)
              return { error: { error: `UNKNOWN_ERROR`, status: `CUSTOM_ERROR` } };
    
            return { data: baseResponseWithUser.data };
          },
        }),
      }),
    });
    
    export const { useGetUserQuery, useLazyGetUserQuery } = extendedApi;