I'm trying to write a generic extension of createAsyncThunk in Typescript, so that I dont have to repeat the thunkApiConfig every time. I'm trying to write it such as this:
type ThunkApiConfig = {
dispatch: MyDispatch:
rejectValue: MyErrorType;
};
export const createAppThunk = <Returned, ThunkArg = void>(
type: string,
payloadCreator: (
arg: ThunkArg,
thunkAPI: GetThunkAPI<ThunkApiConfig>,
) =>
| Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>>
| Returned
| RejectWithValue<GetRejectValue<ThunkApiConfig>>,
) => createAsyncThunk<Returned, ThunkArg, ThunkApiConfig>(type, payloadCreator);
However, this doesnt work since the types GetThunkAPI, RejectWithValue, and GetRejectValue are not exported. Is there any other recommended way of doing this? Or can you export the types needed to extend createAsyncThunk?
I encountered the same problem.
In react-redux types there is exported interface DefaultRootState (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts#L47) which can be augmented by users to add default types for the root state. So instead of reapeating useSelector<AppState, ...>() or creating generic wrapper, we can write once:
declare module 'react-redux' {
interface DefaultRootState extends AppState {}
}
It would be great if something similar will be available in @reduxjs/toolkit. I mean (but this exactly is not possible):
declare module '@reduxjs/toolkit' {
type AsyncThunkConfig = {
state?: AppState;
dispatch?: AppDispatch;
extra?: null;
rejectValue?: HttpError;
}
}
Hmm. While most everything else would usually stay the same, especially the rejectValue would be something that could change on a asyncThunk to asyncThunk basis, so adding a DefaultAsyncThunkConfig that could be overwritten by module augmentation would imho be the wrong approach.
Also note that the react-redux-types are community-driven on DefinitelyTypes and this is the first time I hear of that type - this has probably been added without any of the redux maintainers even noticing. I'm not really sure if I'm a fan of that, to be honest.
But back on topic: I'm evaluating what we could do to make this possible.
I could agree on exporting GetThunkAPI, but I'm a bit hestitant on exporting RejectWithValue and GetRejectValue as I'd like to keep these implementation details.
What would you think about exported types (in addition to GetThunkAPI) like
export type AsnycThunkPayloadCreatorReturnValue<
Returned,
ThunkApiConfig extends AsyncThunkConfig
> =
| Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>>
| Returned
| RejectWithValue<GetRejectValue<ThunkApiConfig>>
export type AsyncThunkPayloadCreator<
Returned,
ThunkArg = void,
ThunkApiConfig extends AsyncThunkConfig = {}
> = (
arg: ThunkArg,
thunkAPI: GetThunkAPI<ThunkApiConfig>
) => AsnycThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig>
I believe that should enable api-wrapping like in the first post.
Faced the same issue today. It would be great to have an official solution for the issue.
@phryneas looks great
Should be resolved by https://github.com/reduxjs/redux-toolkit/releases/tag/v1.3.5 .
I have the same use case; however, as far as I can tell, there's no way to get a type for the return type of createAsyncThunk. (In the above example, this would mean that you can't declare a return type for createAppThunk; the compiler has to infer it.)
This is a problem for me specifically because I'm trying to declare overloads for my equivalent function to createAppThunk, and the typings for these overloads have to have a declared return type because they can't be inferred. But I imagine it would come up in any context in which you have to declare the full type of a wrapper around createAsyncThunk.
@smrq not sure what you're trying to do there, specifically. Can you open a new issue and provide some concrete examples?
Having trouble with this - ben trying to make a wrapper too, but keep getting type errors.
Almost there, apart from any thunk that doesn't require a payload says it s expecting one argument.
Any chance someone provide an example of a wrapped createAsyncThunk with the latest types?
@trickeyd could you link a few typescript playgrounds to see what you are trying to do and what errors you are getting?
"wrapping cAT" could mean so many different ways of wrapping that I doubt that any example here would help you otherwise
Hey @phryneas.
Thanks I really appreciate you taking the time to answer
I'm not really sure how to use a playground with a in conjunction with a lib like redux-toolkit, but I can show you exactly what I am trying to achieve. It is similar to what others are doing in various threads I've seen, tho the exact implementation still alludes me.
export const createMyOwnAsyncThunk = <Returned, ThunkArg = any>(
type: string,
thunk: AsyncThunkPayloadCreator<Returned, ThunkArg>, // <-- very unsure of this - have tried many things here
): AsyncThunk<Returned, ThunkArg, ThunkAPIConfig> => {
return createAsyncThunk<Returned, ThunkArg, ThunkAPIConfig>(
type,
async (arg, thunkAPI) => {
try {
// do some stuff here that happens on every action
return await thunk(arg, thunkAPI)
} catch (err) {
// do some stuff here that happens on every error
return thunkAPI.rejectWithValue(null)
}
},
)
}
It works fine - I just don't seem to have the typings down properly. As I said, the only side effect at the moment is that when i create a thunk with this, which requires no payload i get the following
dispatch(myThink()). // expected 1 argument but got none
@trickeyd see this playground
Aaaaah of course! Amazing thanks!
Most helpful comment
I encountered the same problem.
In
react-reduxtypes there is exported interfaceDefaultRootState(https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts#L47) which can be augmented by users to add default types for the root state. So instead of reapeatinguseSelector<AppState, ...>()or creating generic wrapper, we can write once:It would be great if something similar will be available in
@reduxjs/toolkit. I mean (but this exactly is not possible):