The following code example demonstrates the error I've been having where trying to dispatch an async action created by createAsyncThunk inside another createAsyncThunk was causing TypeScript to complain.
Stripped down simple example:
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';
const bookSlice = createSlice({
name: 'book',
initialState: [
{
name: 'Book Title',
},
],
reducers: {
addBook: (state, action) => [...state, { name: action.payload }],
},
});
const { addBook } = bookSlice.actions;
export const store = configureStore({
reducer: {
books: bookSlice.reducer,
},
devTools: process.env.NODE_ENV !== 'production',
});
export type RootState = ReturnType<typeof store.getState>;
const addUniqueBook = createAsyncThunk<
void,
string,
// Adding anything to the ThunkApiConfig causes the issue when dispatched in another async action.
{
state: RootState;
}
>('addUniqueBook', (newTitle, thunkAPI) => {
if (thunkAPI.getState().books.indexOf({ name: newTitle }) === -1) {
thunkAPI.dispatch(addBook(newTitle));
}
});
// This works fine:
store.dispatch(addUniqueBook('New Book Title'));
export const otherAsyncAction = createAsyncThunk('otherAsyncAction', (arg, thunkAPI) => {
// Type error here:
thunkAPI.dispatch(addUniqueBook('New Book Title'));
// This works, but uses the 'any' type which is very permissive:
thunkAPI.dispatch<any>(addUniqueBook('New Book Title'));
});
Error:
Example.ts
TypeScript error in /home/rjb/Stellar/seac-ui/src/state/Example.ts(44,21):
No overload matches this call.
Overload 1 of 2, '(action: AnyAction): AnyAction', gave the following error.
Argument of type 'AsyncThunkAction<void, string, { state: { books: { name: string; }[]; }; }>' is not assignable to parameter of type 'AnyAction'.
Property 'type' is missing in type 'AsyncThunkAction<void, string, { state: { books: { name: string; }[]; }; }>' but required in type 'AnyAction'.
Overload 2 of 2, '(asyncAction: ThunkAction<Promise<PayloadAction<void, string, { arg: string; requestId: string; }, never> | PayloadAction<unknown, string, { arg: string; requestId: string; aborted: boolean; condition: boolean; }, SerializedError>> & { ...; }, unknown, unknown, AnyAction>): Promise<...> & { ...; }', gave the following error.
Argument of type 'AsyncThunkAction<void, string, { state: { books: { name: string; }[]; }; }>' is not assignable to parameter of type 'ThunkAction<Promise<PayloadAction<void, string, { arg: string; requestId: string; }, never> | PayloadAction<unknown, string, { arg: string; requestId: string; aborted: boolean; condition: boolean; }, SerializedError>> & { ...; }, unknown, unknown, AnyAction>'.
Types of parameters 'getState' and 'getState' are incompatible.
Type 'unknown' is not assignable to type '{ books: { name: string; }[]; }'. TS2769
42 | export const otherAsyncAction = createAsyncThunk('otherAsyncAction', async (arg, thunkAPI) => {
43 | // Type error here:
> 44 | thunkAPI.dispatch(addUniqueBook('New Book Title'));
| ^
45 |
46 | // This works, but uses the 'any' type which is very permissive:
47 | thunkAPI.dispatch<any>(addUniqueBook('New Book Title'));
I'm a bit new to TypeScript, so the error didn't help me much, but I finally realized just now that I can fix the type error by passing the same ThunkApiConfig type argument to both createAsyncThunk calls (i.e. createAsyncThunk<..., ..., {state: RootState;}>)
I don't know if anyone else has run into this problem or not, or maybe I am using an unsupported redux/-thunk design pattern?
If it's not just me, perhaps the documentation or error message could be updated to include a note about dispatching an async action from inside another async action requires the same ThunkApiConfig? (https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk)
One last question, is it a common pattern to have type AppThunkApiConfig: ThunkApiConfig = {state: RootState;} in a common .ts file that is included wherever async actions are created?
Thanks in advance!
Hey @DragonAxe,
You just need to specify the type for dispatch inside of the createAsyncThunk, the same way you do with useDispatch<AppDispatch>(). I think by your message you figured that portion out? In general, you don't really have to provide all of the generics, but as you mention, you can abstract that ThunkApiConfig away if your pattern is the same every time (ex: standardized error messages on APIs could support the same rejectValue declaration or just take an extra generic).
Here's how I would've typed something similar:
export const nestedAsyncThunk = createAsyncThunk(
"counter/nestedAsyncThunk",
async (amount: number, { dispatch }: { dispatch: AppDispatch }) => { // in your example this would've used `AppThunkApiConfig` instead of the { dispatch: AppDispatch } casting
const secondAmount = await dispatch(incrementAsyncThunk(amount));
if (incrementAsyncThunk.fulfilled.match(secondAmount)) {
return amount + secondAmount.payload;
}
}
);
Hi. I have similar error, but if I try to type dispatch, I'm getting another error:
TS7022: 'fetchCodes' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
If I understand correctly, it is because of using this action to declare extra reducers in slice.
@ezhikov this is a circular type reference (the type of RootState depends on the result of createSlice which depends on the result of createAsyncThunk which requires RootState).
The most reliable way to break this is to case the reducer on export like
export const reducer: Reducer<typeof initialState> = slice.reducer
// or
export default slice.reducer as Reducer<typeof initialState>
Thanks for suggestion, but it only adds same error to reducer export. Is there any other way to properly type dispatch inside async thunk?
I would need to see more of your code (flying pretty blind right now), but I'm very sure this will fix it.
Make sure to use that style in all reducer exports that reference your async thunk and to actually use that export for your configureStore call, not that you export it, but import the whole slice for the configureStore call, that wouldn't work.
@phryneas I ran into same problem. Here is code: https://codesandbox.io/s/todo-forked-28g96?file=/src/store/user/slice.ts
Can you please look? I am new to redux etc
@dragneelfps add curly braces around your extraReducers method body.

@ezhikov this is a circular type reference (the type of
RootStatedepends on the result ofcreateSlicewhich depends on the result ofcreateAsyncThunkwhich requiresRootState).
The most reliable way to break this is to case the reducer on export likeexport const reducer: Reducer<typeof initialState> = slice.reducer // or export default slice.reducer as Reducer<typeof initialState>
Is this documented anywhere? If not I think it ought to be. I have spent a lot of hours trying to debug this and this is the first time I have seen this solution, which appears to work perfectly.
@DanielOschrin doesn't look like it's in our "Usage with TS" page atm. Could you file a PR to add that to the store setup section?
Most helpful comment
@dragneelfps add curly braces around your

extraReducersmethod body.