3

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.

Rychu
  • 765
  • 1
  • 4
  • 20

2 Answers2

2

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;
Rychu
  • 765
  • 1
  • 4
  • 20
2

I followed https://redux-toolkit.js.org/rtk-query/usage-with-typescript#typing-a-basequery so I have a QueryReturnValue type

export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =
| {
  error: E
  data?: undefined
  meta?: M
}
| {
  error?: undefined
  data: T
  meta?: M
}

Then inside a queryFn I do the following which resolves TS compilation errors.

const result = await baseQuery(`/user/me`);
return result as QueryReturnValue<UserResponse, unknown, unknown>;

What I then did was create a helper function to make this less verbose.

// Somewhere all api slices can import
export function someHelperForTS<T>(result: QueryReturnValue<unknown, unknown, unknown>) {
    return result as QueryReturnValue<T, unknown, unknown>;
}

// Within queryFn
const result = await baseQuery(`/user/me`);
return someHelperForTS<UserResponse>(result);

I don't love it as I have to declare the same response type in both build.query (build.query<UserResponse) and someHelperForTS (someHelperForTS<UserResponse>).

Steven
  • 1,670
  • 4
  • 17
  • 26