Trying to download a file, losing return value type in responseHandler in queryFn when using baseQueryWithReauth

Viewed 31

I am trying to download a file using a mutation endpoint which wraps a queryFn. The code for the mutation looks like:

downloadTournamentTRF: builder.mutation<{filename: string}, {id: Tournament["id"]}>({
    queryFn: async ({id}, api, extraOptions, baseQuery) => {
        return baseQuery({
            url: `/tournament/trf/${id}`,
            responseHandler: async (response) => {
                if (response.ok) {
                    const filename = await saveResponseUsingHiddenElement(response);
                    return {data: {filename}};
                } else {
                    return {error: {status: response.status, message: response.statusText}};
                }
            },
            cache: "no-cache"
        });
    }
}),

This generates an error on the queryFn type:

TS2322: Type '({ id }: { id: number; }, api: BaseQueryApi, extraOptions: {}, baseQuery: (arg: string | FetchArgs) => MaybePromise<QueryReturnValue<unknown, FetchBaseQueryError, {}>>) => Promise<...>' is not assignable to type '(arg: { id: number; }, api: BaseQueryApi, extraOptions: {}, baseQuery: (arg: string | FetchArgs) => MaybePromise<QueryReturnValue<unknown, FetchBaseQueryError, {}>>) => MaybePromise<...>’.
   Type 'Promise<QueryReturnValue<unknown, FetchBaseQueryError, {}>>' is not assignable to type 'MaybePromise<QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>>’.
     Type 'Promise<QueryReturnValue<unknown, FetchBaseQueryError, {}>>' is not assignable to type 'PromiseLike<QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>>’.
       Types of property 'then' are incompatible.
         Type '<TResult1 = QueryReturnValue<unknown, FetchBaseQueryError, {}>, TResult2 = never>(onfulfilled?: ((value: QueryReturnValue<unknown, FetchBaseQueryError, {}>) => TResult1 | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '<TResult1 = QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>, TResult2 = never>(onfulfilled?: ((value: QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>) => TResult1 | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 mo...’.
           Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
             Types of parameters 'value' and 'value' are incompatible.
               Type 'QueryReturnValue<unknown, FetchBaseQueryError, {}>' is not assignable to type 'QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>’.
                 Type '{ error?: undefined; data: unknown; meta?: {} | undefined; }' is not assignable to type 'QueryReturnValue<{ filename: string; }, FetchBaseQueryError, unknown>’.
                   Type '{ error?: undefined; data: unknown; meta?: {} | undefined; }' is not assignable to type '{ error?: undefined; data: { filename: string; }; meta?: unknown; }’.
                     Types of property 'data' are incompatible.
                       Type 'unknown' is not assignable to type '{ filename: string; }’.

In our baseQuery, we are using a baseQueryWithReauth function based on the code from from documentation here. Our code is virtually unchanged and looks like:

const baseQueryWithReauth: BaseQueryFn<string | FetchArgs,
    unknown,
    FetchBaseQueryError> = async (args, api, extraOptions) => {
    [...]
};

The issue appears to be that the second type argument to the BaseQueryFn generic is set to unknown and that seems to squelch the return type causing the error.

Is there a way I can change that type definition to preserve the return type and clean up the error?

Thanks!

2 Answers

baseQuery will always return unknown - it gets data from the server and there is no way of knowing what that is. If you as a programmer know better here, you can always do a as Something - or you write a custom assertion function or use something like zod to parse & verify the return type.


On second look, you were just overdoing it - queryFn needs to return an object in the shape {data} or {error}, but transformResponse doesn't need to. And it doesn't look like you need queryFn here at all.

This should do:


export const chessMasterApi = createApi({
  reducerPath: 'chessMasterApi',
  baseQuery: baseQueryWithReauth,
  tagTypes: ['Audit'],
  endpoints: (builder) => ({
    downloadTournamentTRF: builder.mutation<
      { filename: string },
      { id: Tournament['id'] }
    >({
      query: ({ id }) => ({
        url: `/tournament/trf/${id}`,
        responseHandler: async (response) => {
          return await saveResponseUsingHiddenElement(response);
        },
        cache: 'no-cache',
      }),
    }),
  }),
});

That's really great and works nicely -- thanks for your help!

The one change I had to make was to get it to handle errors correctly:

export const chessMasterApi = createApi({
  reducerPath: 'chessMasterApi',
  baseQuery: baseQueryWithReauth,
  tagTypes: ['Audit'],
  endpoints: (builder) => ({
    downloadTournamentTRF: builder.mutation<
      { filename: string },
      { id: Tournament['id'] }
    >({
      query: ({ id }) => ({
        url: `/tournament/trf/${id}`,
        responseHandler: async (response) => {
          if (response.ok) {
            return await saveResponseUsingHiddenElement(response);
          } else {
            return {message: response.statusText};
        },
        cache: 'no-cache',
      }),
    }),
  }),
});
Related