16
votes

Say I have code like so:

import { Action, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';

interface StateTree {
  field: string;
}

function myFunc(action: Action | ThunkAction<void, StateTree, void>,
                dispatch: Dispatch<StateTree>) {
  dispatch(action); // <-- This is where the error comes from
}

...I get this error from the TypeScript compiler:

ERROR in myFile.ts:x:y
TS2345: Argument of type 'Action | ThunkAction<void, StateTree, void>' is not assignable to parameter of type 'Action'.
  Type 'ThunkAction<void, StateTree, void>' is not assignable to type 'Action'.
  Property 'type' is missing in type 'ThunkAction<void, StateTree, void>'.

I believe the problem is because of the way the redux-thunk type definition file augments the redux Dispatch interface and the inability for TypeScript to know which definition of Dispatch to use.

Is there a way around this?

4

4 Answers

28
votes

ThunkAction signature changed with latest version (now is ThunkAction<void, Your.Store.Definition, void, AnyAction>) and unless some evil double casting (action as {} as Action), the more elegant way I found is to define the redux dispatch as a ThunkDispatch like this:

import { applyMiddleware, Store, createStore, AnyAction } from 'redux';
import logger from 'redux-logger';
import thunk, { ThunkDispatch } from 'redux-thunk';

import { Redux } from '../definitions';
import rootReducer from './reducers';
import { bootstrap } from './actions';

export default function configureStore() {

    const middleware = applyMiddleware( thunk, logger );

    const store: Store<Redux.Store.Definition> = createStore(rootReducer, middleware);

    // Here the dispatch casting:
    (store.dispatch as ThunkDispatch<Redux.Store.Definition, void, AnyAction>)( bootstrap() );

    return store;

}

In case someone else is looking for an updated answer! ^^

22
votes

Time has passed and many things have changed since this question and various answers were posted. However, I found that none of the answers seemed satisfactory to me because the first two (michael-peyper and pierpytom) involved recasting/redefining which felt weird. The third (joaoguerravieira) seemed better since it didn't involve either of those, but it was unclear, to me at least, how it solved the problem.

This is what seemed most helpful to me when I ran into a problem which I think was practically identical to this: how to get a properly typed "dispatch" method on the created redux store. That is, how to get the TypeScript compiler to agree that store.dispatch could dispatch either Actions or ThunkActions. Even if this is not exactly the same problem being asked about in the original question (but I think it might be), all search engine queries about my problem kept leading me back to this post so I thought it might be helpful to put my solution here.

I have always found it super difficult to find the right types to use for things when using redux (maybe I'm just dumb) so for a long time I always just created my store like this:

createStore(
    combineReducers(stuff),
    defaultState,
    applyMiddleware(thunkMiddleware));

...which always put me in the situation where I could call store.dispatch on thunks but the TypeScript compiler yelled at me even though it would still work at runtime. Bits of each answer finally lead me to what I believe is the most up-to-date and no-casting way of solving the problem.

The typing of the dispatch method on the store object is dictated by what the call to redux's createStore returns. In order to have the right type on the store's dispatch method, you have to set the type parameters correctly on the call to applyMiddleware (which you either directly or eventually pass as the third parameter to createStore). @joaoguerravieira's answer led me to look in this direction. In order to get the dispatch method to have the right type to dispatch either ThunkAction or Action, I had to call createStore/applyMiddleware like this:

createStore(
    combineReducers(stuff),
    defaultState,
    applyMiddleware<DispatchFunctionType, StateType>(thunkMiddleware));

where

type DispatchFunctionType = ThunkDispatch<StateType, undefined, AnyAction>

By doing this, I got a store of this type:

Store<StateType, Action<StateType>> & { dispatch: DispatchFunctionType };

...which gets me a store.dispatch function of this type:

Dispatch<Action<any>> & ThunkDispatch<any, undefined, AnyAction>

...which can successfully dispatch an Action or a ThunkAction without yelling about type and without any redefinitions/casting.

Properly setting the type parameters on the call to applyMiddleware is critical!

10
votes

These are the correct typings: https://github.com/reduxjs/redux-thunk/blob/master/test/typescript.ts

Most notably:

const store = createStore(fakeReducer, applyMiddleware(thunk as ThunkMiddleware<State, Actions>));

applyMiddleware will already override the dispatch with a ThunkDispatch.

4
votes

I think you are correct in that despite being able to handle both types, typescript cannot work out which overload to use.

I think the best option for you is to cast back to the desired type when calling dispatch

function myFunc(action: Action | ThunkAction<void, StateTree, void>, 
                dispatch: Dispatch<StateTree>) {
  if (action instanceof ThunkAction<void, StateTree, void>) {
    dispatch(action as ThunkAction<void, StateTree, void>);
  } else {
    dispatch(action as Action);
  }
}

I hope I'm wrong and there is a better way to achieve this.