reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.7k stars 1.17k forks source link

Is thunkAPI type exported? #1389

Closed girafferiel closed 3 years ago

girafferiel commented 3 years ago

I have a helper function for me to call my backend API. If the error is jwt expired, I have to get my access token & call the API again one more time.

export async function createItem(form: { name: string, notes: string }): Promise<Item> {
  const request = await HttpHelper.makeRequest('POST', `${baseUrl}/item`, form);

  try {
    const response: AxiosResponse<{ item: Item }> = await axios(request);
    return response.data.item;
  } catch (error) {
    if (error.response) {
      throw error.response.data;
    } else {
      throw 'Unknown error';
    }
  }
}
import {
  createItem as createItemApi,
} from 'clients/itemService';
import { execute } from 'utils/jwtExpiryHelper';

export const createItem = createAsyncThunk(
  'item/createItem',
  async(form: { name: string, notes: string }, thunkAPI) => {
    return execute(thunkAPI, () => createItemApi(form));
  }
);
// utils/jwtExpiryHelper.ts
import SecureStore from 'utils/secureStore';
import { updateLoginState } from 'reduxActions/auth/authReducer';
import { createAccessToken } from 'clients/auth';

export async function execute(api: any, callback: (...funcArgs: any) => Promise<any>): Promise<any> {
  try {
    return await callback();
  } catch(e) {
    if (e.code !== 'jwt-expired') {
      return api.rejectWithValue(e);
    }
  }

  try {
    const jwt = await createAccessToken();
    await SecureStore.setItem('access-token', jwt);
  } catch(e) {
    api.dispatch(updateLoginState(false));
    throw e;
  }

  return callback();
}

I want to be able to call dispatch and rejectWithValue since I have custom error that I want to display in my local component...But im not sure what type is thunkAPI. thanks

phryneas commented 3 years ago

That would be

BaseThunkAPI<
  State,
  Extra,
  Dispatch,
  RejectValue,
  RejectedMeta,
  FulfilledMeta
>

which I admit is a bit unwieldy, but it has a lot of stuff going in there, so unfortunately not a lot to be done about that.

By the way, have you taken a look at https://redux-toolkit.js.org/rtk-query/overview if it fits your use case? That might already be doing a lot of the stuff you are doing by hand here.

girafferiel commented 3 years ago

ok i will check the RTK.. but out of curiosity, how do I import the basethunkapi? I've tried

import { BaseThunkAPI } from '@reduxjs/toolkit'; it said Module '"@reduxjs/toolkit"' has no exported member 'BaseThunkAPI'

I tried import { BaseThunkAPI<State,Extra,Dispatch,RejectValue,RejectedMeta,FulfilledMeta> } from '@reduxjs/toolkit'; it said string literal expected

thanks

phryneas commented 3 years ago

Argh, sorry, it's exported from the file but not the lib - no wonder, given how unwieldy it is.

In that case you'd have to do something along the lines of

type ThunkApi = Parameters<Parameters<typeof createAsyncThunk>[1]>[1]
girafferiel commented 3 years ago

thanks a lot

charlieforward9 commented 8 months ago

@phryneas

type ThunkApi = Parameters<Parameters<typeof createAsyncThunk>[1]>[1]

Is this still the recommended method? Looks a bit janky. I was depending on import { BaseThunkAPI } from "@reduxjs/toolkit/dist/createAsyncThunk"; but just upgraded my Redux versions and that no longer works...

I am trying to store the ThunkAPI typed as so

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export type AppError = {
  message: string;
  feature?: string;
  action?: string;
  stack?: string;
};
export type ThunkAPI = BaseThunkAPI<RootState, unknown, AppDispatch, AppError>;
markerikson commented 8 months ago

@charlieforward9 can you give an example of how and where you're trying to use that type?

charlieforward9 commented 8 months ago

I was using it to properly type the createAsyncThunk method thunkAPI object, since I had them in separate files, however I decided to put them together so now it's automatically typed. So you can disregard this. Thank you.

renchap commented 2 months ago

Here is an example of how this type is useful to us in Mastodon: https://github.com/mastodon/mastodon/blob/main/app/javascript/mastodon/store/typed_functions.ts#L28

I tried alternatives, but havent been able to find a better way of doing this.

EskiMojo14 commented 2 months ago

@renchap

type AppThunkConfig = {
  state: RootState;
  dispatch: AppDispatch;
  rejectValue: AsyncThunkRejectValue;
  fulfilledMeta: AppMeta;
  rejectedMeta: AppMeta;
};

const createBaseAsyncThunk = createAsyncThunk.withTypes<AppThunkConfig>();

export function createThunk<Arg = void, Returned = void>(
  name: string,
  creator: AsyncThunkPayloadCreator<Returned, Arg, Omit<AppThunkConfig, "fulfilledMeta" | "rejectMeta">> 
  options: AppThunkOptions = {},
) {
  return createBaseAsyncThunk(
    name,
    async (
      arg: Arg,
      api,
    ) => {
      const { fulfillWithValue, rejectWithValue } = api
      try {
        const result = await creator(arg, api);

        return fulfillWithValue(result, {
          skipLoading: options.skipLoading,
        });
      } catch (error) {
        return rejectWithValue({ error }, { skipLoading: true });
      }
    },
    {
      getPendingMeta() {
        if (options.skipLoading) return { skipLoading: true };
        return {};
      },
    },
  );
}
renchap commented 2 months ago

Thanks for this, it allowed me to figure out that GetThunkAPI was exported and allowed us to get what we needed: https://github.com/mastodon/mastodon/pull/31312