redux-thunk: TypeScript Error in Middleware

The overloading of Dispatch by redux-thunk breaks middlewares that only handle standard action objects (as apposed to thunks).

Example:

import {Action, Dispatch, Middleware, Store} from 'redux';
import {IAppState} from '../reducers';

export const middleware: Middleware = (store: Store<IAppState>) => (next: Dispatch<IAppState>) => (action: Action) => {
  // Do something with the action ...
  next(action);
};

The resulting error is:

Type '(store: Store<IAppState>) => (next: Dispatch<IAppState>) => (action: Action) => void' is not assignable to type 'Middleware'.
  Type '(next: Dispatch<IAppState>) => (action: Action) => void' is not assignable to type '(next: Dispatch<any>) => Dispatch<any>'.
    Type '(action: Action) => void' is not assignable to type 'Dispatch<any>'.
      Types of parameters 'action' and 'asyncAction' are incompatible.
        Type '(dispatch: Dispatch<any>, getState: () => any, extraArgument: any) => any' is not assignable to type 'Action'.
          Property 'type' is missing in type '(dispatch: Dispatch<any>, getState: () => any, extraArgument: any) => any'.

A possible fix to remove the compiler error would be to declare a union type for the Action, i.e. Action | Redux.ThunkAction<any, IAppState, any>:

export const middleware: Middleware = (store: Store<IAppState>) => (next: Dispatch<IAppState>) => (action: Action | Redux.ThunkAction<any, IAppState, any>) => {
  // Do something with the action ...
  next(action as Action);
};

But this is incorrect if your middleware only handles Action!

The better solution would be to declare a Dispatch interface within the redux-thunk module that extends Redux.Dispatch. This Dispatch could then be used in action creators that return a ThunkAction:

import {IAppState} from '../reducers';
import {ThunkAction, Dispatch} from 'redux-thunk';

export function thunkedActionCreator(): ThunkAction<void, IAppState, void> {
  return (dispatch: Dispatch<IAppState>, getState: () => IAppState): void => {
    // Do something async and dispatch actions or other thunks ...
  };
}

Middlewares on the other hand would use Redux.Dispatch (see middleware example above).

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 3
  • Comments: 37

Commits related to this issue

Most upvoted comments

I think the original issue can be resolved with improved typings based on https://github.com/reactjs/redux/pull/2563 which hopefully lands soon.

not sure if u r into ng2, but if u r: Angular 2 Kitchen sink: http://ng2.javascriptninja.io and source@ https://github.com/born2net/Angular-kitchen-sink Regards,

Sean

From whichever is first in the middleware chain.

IMO is not a proper picture. Consider these:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import prom from 'redux-promise';
import rootReducer from './reducers';
import { Store } from '???'

// Note: this API requires redux@>=3.1.0
const store: Store = createStore(
  rootReducer,
  applyMiddleware(thunk, prom)
);

const store2: Store = createStore(
  rootReducer,
  applyMiddleware(prom, thunk)
);

Where should the Store come from? the order of the middleware should have nothing to do with where you get the Store from. That’s why it is an augmentation.

I can understand what is your confusion.

If it is written in typescript, you would do module augmentation. And the Store is still imported from redux instead of redux-thunk.

If it follows your implementation, then other packages which augment Dispatch would need to do the same thing. If user wants to use both redux-thunk and redux-promise, where should he/she gets the Store from?

Well, I think that’s a problem. Store does not belongs to redux-thunk, so we should not export and require user to import Store from redux-thunk.

Think about it this way: If redux and redux-thunk are written in TS to begin with, what should have happened? It is an augmentation of a method and should be remain as so.

My two cents, 🌷