Skip to content

TypeScript: type definitions for async middleware #2602

Closed
@alexburner

Description

@alexburner

Do you want to request a feature or report a bug?
Bug?

What is the current behavior?
In the definitions file, the interface for Middleware is:

interface Dispatch<S> {
    <A extends Action>(action: A): A;
}

export interface MiddlewareAPI<S> {
  dispatch: Dispatch<S>;
  getState(): S;
}

interface Middleware {
  <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

Since the final function in the middleware thunk is typed as Dispatch<S>, it is required to synchronously return an A action. However, custom middleware should support other behaviors. In the definitions file:

 * Middleware wraps the base dispatch function. It allows the dispatch
 * function to handle async actions in addition to actions. Middleware may
 * transform, delay, ignore, or otherwise interpret actions or async actions
 * before passing them to the next middleware.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar.

If I attempt to add a type to the vanillaPromise from the middleware examples:

import { Middleware } from 'redux'
const middleware: Middleware = store => next => action => {
  if (typeof action.then !== 'function') {
    return next(action)
  }

  return Promise.resolve(action).then(store.dispatch)
}

TypeScript throws the following errors:

ERROR in [at-loader] ./src/index.tsx:23:7 
    TS2322: Type '<S>(store: MiddlewareAPI<S>) => (next: Dispatch<S>) => <A extends Action>(action: A) => A | Promi...' is not assignable to type 'Middleware'.
  Type '(next: Dispatch<S>) => <A extends Action>(action: A) => A | Promise<A>' is not assignable to type '(next: Dispatch<S>) => Dispatch<S>'.
    Type '<A extends Action>(action: A) => A | Promise<A>' is not assignable to type 'Dispatch<S>'.
      Type 'A | Promise<A>' is not assignable to type 'A'.
        Type 'Promise<A>' is not assignable to type 'A'.

ERROR in [at-loader] ./src/index.tsx:24:21 
    TS2339: Property 'then' does not exist on type 'A'.

What is the expected behavior?

I'd expect the Middleware interface to allow returning a promise-wrapped action, or void, or etc. I'm not sure the actual fix for this... Maybe something like:

export interface MiddlewareDispatch<S> {
    <A extends Action>(action: Promise<A> | A): Promise<A | void> | A | void;
}

export interface MiddlewareAPI<S> {
  dispatch: MiddlewareDispatch<S>;
  getState(): S;
}

export interface Middleware {
  <S>(api: MiddlewareAPI<S>): (next: MiddlewareDispatch<S>) => MiddlewareDispatch<S>;
}

This solves the type errors for the vanillaPromise example, and would also solve the problems with returning void. However I'm not certain it would cover all middleware use cases.

Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?

I'm using redux 3.7.2 and typescript 2.5.2. This is the first time I've tried this.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions