Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript: type definitions for async middleware #2602

Closed
alexburner opened this issue Sep 11, 2017 · 9 comments
Closed

TypeScript: type definitions for async middleware #2602

alexburner opened this issue Sep 11, 2017 · 9 comments

Comments

@alexburner
Copy link

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.

@alexburner
Copy link
Author

Actually, my suggestion isn't very good, because it complicates more simple uses. If you have:

const maybeVoid: Middleware = store => next => action => {
  if (action.type === 'IgnoreMe') {
    return
  }

  next(action)
}

TypeScript throws an error, because action may be a Promise:

ERROR in [at-loader] ./src/index.tsx:33:14 
    TS2339: Property 'type' does not exist on type 'A | Promise<A>'.
  Property 'type' does not exist on type 'Promise<A>'.

@alexburner
Copy link
Author

Maybe something with generics? Like:

export interface MiddlewareDispatch<S, A> {
    (action: A): A;
}

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

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

That supports both of the previous middleware examples:

const vanillaPromise: Middleware<State, Action | Promise<Action>> = 
  store => next => action => {
    if (typeof (action as Promise<Action>).then !== 'function') {
      return next(action)
    }

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

const maybeVoid: Middleware<State, Action | void> =
  store => next => action => {
    if (!action) return
    if (action.type === 'IgnoreMe') {
      return
    }

    next(action)
  }

@ghost
Copy link

ghost commented Oct 20, 2017

The current definition for Middleware is:

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

This declares that a Middleware must be a generic function, which is probably not intended. This is an error when using newer versions of TypeScript that have strict generic checks (microsoft/TypeScript#16368).
If you meant that a middleware must be of type MiddlewareAPI<S> => Dispatch<S> => Dispatch<S> for some S but not necessarily for all, the type should be Middleware<S>.

@timdorr
Copy link
Member

timdorr commented Oct 23, 2017

@alexburner Does the typing on the next branch solve this for you by chance? https://github.com/reactjs/redux/blob/next/index.d.ts

@timdorr
Copy link
Member

timdorr commented Feb 15, 2018

Closing due to inactivity.

@timdorr timdorr closed this as completed Feb 15, 2018
@inakiarroyo
Copy link

inakiarroyo commented May 21, 2018

@timdorr this thread was closed, but is currently there any alternative or fix to this issue? I am having the exact same problem.

@timdorr
Copy link
Member

timdorr commented May 21, 2018

Check out the typings in 4.0.

@samuelcastro
Copy link

Is there any documentation explaining the proper way to use types for custom middlewares?

@willkuerlich
Copy link

willkuerlich commented Nov 21, 2018

I've also been looking for an redux-thunk middleware strict integration example for a reasonable amount of time, but no luck yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants