Search code examples
typescriptreduxaxiosredux-toolkitredux-thunk

Redux createAsyncThunkFromAPI wrapper issue


https://stackblitz.com/edit/react-ts-rkekhf?file=app/redux/asyncThunkFromFetch.ts

We use the following helper to create thunks:

import { createAsyncThunk } from '@reduxjs/toolkit';
import axios, { AxiosPromise, AxiosResponse } from 'axios';
import { store } from './store';

export const createAsyncThunkFromAPI = <ResponseSchema, RequestParams>(
  typePrefix: string,
  apiFunction: ApiFunction<RequestParams, ResponseSchema>
) => {
  return createAsyncThunk<ResponseSchema, RequestParams>(
    typePrefix,
    async (args, thunkApi) => {
      try {
        const response = await apiFunction(args);
        return response.data;
      } catch (e) {
        return thunkApi.rejectWithValue(e);
      }
    }
  );
};

type ApiFunction<RequestParams, ResponseSchema> = (
  axiosParams: RequestParams
) => AxiosPromise<ResponseSchema>;

export const getMobileOperatorsAPI = (): Promise<AxiosResponse<string[]>> => {
  return axios.get('https://api.com');
};

export default createAsyncThunkFromAPI;

const sliceName = 'mobile-operators';
export const fetchMobileOperators = createAsyncThunkFromAPI(
  `${sliceName}/fetchMobileOperators`,
  getMobileOperatorsAPI
);

store.dispatch(fetchMobileOperators()); //Expected 1 arguments, but got 0.ts(2554)
store.dispatch(fetchMobileOperators({})); //Ouch!!!

One thing I hate about it, you cannot pass zero arguments to a thunk function created by the helper. However in our case it's still more convenient to use it instead of bare createAsyncThunk from @redux/toolkit

I tried to make a default parameter for payload creator with no success. But I don't really understand what I'm doing here.

How to tweak createAsyncThunkFromAPI to infer args from API function?

How to implement the same idea in a proper way?

What knowledge do I miss to be able to resolve this problem by myself?


Solution

  • <ResponseSchema, RequestParams = never>

    You are very close and just missing one tiny little thing. Your thunk factory will infer the type for the generics ResponseSchema and RequestParams by looking at the types of the apiFunction. This particular apiFunction has no argument so the RequestParams is getting inferred as unknown. We want to make sure that it gets inferred as never instead. Therefore we add a default value RequestParams = never. This means that no args will be accepted when the RequestParams cannot be inferred.

    export const createAsyncThunkFromAPI = <ResponseSchema, RequestParams = never>(
    

    After making this change, the desired usage has no error and your workaround solution does have an error.

    store.dispatch(fetchMobileOperators()); // okay!
    store.dispatch(fetchMobileOperators({})); // Expected 0 arguments, but got 1.(2554)
    

    There's one last thing to check which is to make sure that it still works properly when there is a required argument. I added an additional example and sure enough it works perfectly. We get an error when the argument is missing and no error when an argument is provided.