Redux-toolkit: Typescript error when dispatching a thunk: Argument of type 'ThunkAction<void, CombinedState<...>>' is not assignable to parameter of type 'AnyAction'.

Created on 31 May 2020  路  9Comments  路  Source: reduxjs/redux-toolkit

I've followed the docs to setup typescript typings in redux app and struggled with this strange error.

Full typescript error:

Argument of type 'ThunkAction<void, CombinedState<{ MY_APP_STATE }>, unknown, Action<...>>' is not assignable to parameter of type 'AnyAction'.
  Property 'type' is missing in type 'ThunkAction<void, CombinedState<{ MY_APP_STATE }; }>, unknown, Action<...>>' but required in type 'AnyAction'.ts(2345)

The types that I use:

/* Core */
import { Action, ThunkAction } from '@reduxjs/toolkit';
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';

/* Instruments */
import { rootReducer } from '../rootReducer';
import { initializeStore } from '../store';
import { authReducer } from '../domains/auth/reducer';

const store = initializeStore();

export type ReduxState = ReturnType<typeof rootReducer>;
export type AuthStateType = ReturnType<typeof authReducer>;

export type TypedDispatch = typeof store.dispatch;
export type TypedThunk<R = void> = ThunkAction<R, ReduxState, unknown, Action>;

export const useTypedDispatch = () => useDispatch<TypedDispatch>();
export const useTypedSelector: TypedUseSelectorHook<ReduxState> = useSelector;

How thunk is used with dispatch:

...
import {
    useTypedDispatch,
} from '@/lib/redux';

const LoginPage: NextPage = () => {
    const dispatch = useTypedDispatch();
    useEffect(() => {
        ...
        await dispatch(signInAsync(decodedEmail, decodedPassword)); // Error here
        ...

Pretty much strange because dispatch that is available inside of a thunk function seems to work okay.
Tried to mark third argument of ThunkActionunknown, Action> as unknown as suggested one of the issues but that did't worked for me.

Most helpful comment

TypeScript is type puzzle.

All 9 comments

How are your initializeStore and signInAsync looking?

@phryneas initializeStore is a function that retruns configureStore invocation (I use nextjs with redux and apollo bindings`

/* Core */
import { configureStore } from '@reduxjs/toolkit';

/* Instruments */
import { rootReducer } from './rootReducer';
import { middleware } from './middleware';
import { initialState as authInitialState } from './domains/auth/reducer';

const initialState = {
    auth: authInitialState,
};

export const initializeStore = (preloadedState = initialState) => {
    return configureStore({
        reducer: rootReducer,
        preloadedState,
        middleware,
    });
};

And here is signInAsync:

/* Instruments */
import { TypedThunk } from '@/lib/redux';

/* Actions */
import {
    setAuthFetching,
    setAuthStep,
    setFirstLogin,
    setAuthenticated
} from '../actions';

export const signInAsync = (
    email: string,
    password: string,
): TypedThunk => async dispatch => {
    dispatch({ type: 'SIGN_IN_ASYNC' });
    dispatch(setAuthFetching(true));

    // ... rest of the thunk

    dispatch(setAuthenticated(true));
    await dispatch(saveRefreshTokenAsync()); // there is no `dispatch` error with thunk here

   // ... rest of the thunk
};

Okay, next step is your middleware declaration (I had hoped that would already be in there) - because the Dispatch type correlates directly to the middleware array given there. Also, please hover over that middleware array and determine what type it currently is.
It should be something along the lines of Array<Middleware<{}, ReduxState, {}> | ThunkMiddleware<ReduxState, AnyAction, unknown>> - so ThunkMiddleware should be explicitly mentioned as possible array member.

@phryneas Oh yes, missed that, sorry. Here is the middleware file:

/* Core */
import { Middleware, getDefaultMiddleware } from '@reduxjs/toolkit';

/* Instruments */
import { ReduxState } from './types';

const middleware: Middleware[] = [ ...getDefaultMiddleware<ReduxState>() ];

if (__DEV__) {
    const { createLogger } = require('redux-logger');

    const logger = createLogger({
        duration:  true,
        timestamp: false,
        collapsed: true,
        colors:    {
            title:     () => '#139BFE',
            prevState: () => '#1C5FAF',
            action:    () => '#149945',
            nextState: () => '#A47104',
            error:     () => '#ff0005',
        },
        predicate: () => process.browser,
    });

    middleware.push(logger);
}

export { middleware };

And here is on-hover tooltip of middleware variable:

const middleware: Middleware<{}, any, Dispatch<AnyAction>>[]

UPD: may it be because if MIddleware[] specified explicitly?
I've removed that type and it looks like an error disappeared.

So most likely my issue may be resolved!
How magically works 芦ask someone a question禄 move.
Thank you very much @phryneas for pointing questions! 馃檹馃徏

Yup, you got it!

Type erasure by explicitly defining a type ;)

Maybe that's a good thing in general: TS is great at inferring types, you should only ever define variable types if really necessary ;)

TypeScript is type puzzle.

For anyone who is using thunks and trying to add redux-saga to their middleware.

If your dispatch type is thrown off when adding saga middleware causing your thunk dispatches to give an error, here is a fix.

Bad:

const sagaMiddleware = createSagaMiddleware();

const middleware = [
  sagaMiddleware,
  ...getDefaultMiddleware(),
];

const store = configureStore({
  reducer: rootReducer,
  middleware,
});

This will cause the type of middleware to be inferred inaccurately, causing dispatch to give an error when dispatching thunks.

Instead you should use callback notation which will correctly retain types in middleware array:

const store = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(sagaMiddleware)
});

@boy51 : actually, you should _not_ manually spread the middleware. Instead, call getDefaultMiddleware().concat(myMiddleware), which is a specially-typed version of Array.concat() that better retains the types.

Thanks, I updated my comment :)

Finally I can sleep peacefully knowing my dispatch is correctly typed.

Was this page helpful?
0 / 5 - 0 ratings