Skip to content

Typescript & Middleware extensions #1712

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

Closed
pfgray opened this issue May 10, 2016 · 7 comments
Closed

Typescript & Middleware extensions #1712

pfgray opened this issue May 10, 2016 · 7 comments

Comments

@pfgray
Copy link

pfgray commented May 10, 2016

Hi,

I'm wondering how the typescript type definitions for extension via middleware are supposed to work (when extending the types of Actions that are possible).

For instance, let's take the module redux-thunk (one of the simplest redux middlewares).

It allows you to dispatch a function with type: (Dispatch, ()=>S) => void, (that is, a function whose first parameter is the dispatch function, and whose second parameter is the getState function).

suppose I have a trivial type definition for redux-thunk:

declare module "redux-thunk" {
  import { Middleware } from 'redux';
  const thunkMiddleware: Middleware;
  export default thunkMiddleware;
}

And I use it in my app to create a store like:

import { Reducer, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const myReducer: Reducer<number> = (count, action) => count;

const myStore = createStore(myReducer, applyMiddleware(thunk));

I can't dispatch a redux-thunk function:

// fails with: Argument of type '(dispatch: any) => number' is not assignable to parameter of type 'Action'.
myStore.dispatch(dispatch =>
  setTimeout(() =>
    dispatch({
      type: "INCREMENT_COUNT"
    })
  , 500)
)

This is a complex api to account for in a type system, since the signature of dispatch changes depending on what middleware is loaded:

<A extends Action> dispatch(action: A)

becomes:

dispatch(action: Action | ThunkAction)

(where ThunkAction is (Dispatch, ()=>S) => void from above.

I'm not really sure immediately how to address this, but I was just wondering if there is an intended way to work with redux middleware.

cc'ing @aikoven @Igorbek @use-strict

@aikoven
Copy link
Collaborator

aikoven commented May 11, 2016

@pfgray See #1537
Basically you just augment Dispatch interface whenever middleware adds support for dispatching new type of things.

@garthk
Copy link

garthk commented Jun 3, 2016

@aikoven, please give explicit instructions. I think I've put enough time into trying to fill in the blanks. I'm not making any progress. Given redux supplies typings and refers to redux-thunk in the documentation, surely someone knows how to make the two play nicely together under TypeScript.

As proof of effort, let's start with the obvious:

  • npm install redux
  • typings install npm~redux-thunk
import thunkMiddleware from 'redux-thunk';
console.log(thunkMiddleware)

Result: error TS2304: Cannot find name 'thunkMiddleware' in line 2. Looking in typings/modules/redux-thunk/index.d.ts, it turns out the source is andrew-w-ross/typings-redux, which the author deprecated in favour of the official redux typings. Which, sadly, don't include redux-thunk. Perhaps the other typings search redux-thunk result works:

  • typings remove redux-thunk
  • typings install --global dt~redux-thunk

This one comes from gaearon/redux-thunk via DefinitelyTyped. Success?

typings/globals/redux-thunk/index.d.ts(4,36): error TS2503: Cannot find namespace 'Redux'.
typings/globals/redux-thunk/index.d.ts(6,21): error TS2503: Cannot find namespace 'Redux'.

Aah, it'll only work if you use the global DefinitelyTyped typings for redux. Damn.

@HenrikBechmann offers, in DefinitelyTyped/DefinitelyTyped#6231:

I replaced the tsd-installed definition with this:

declare module 'redux-thunk' {
    import {MiddlewareArg} from 'redux'
    export default function(obj: MiddlewareArg): Function
}

Oh, that'll be easy to slot in, we lie to ourselves after the misadventures to date.

  • typings remove --global redux-thunk
  • cat > typings-local/redux-thunk.d.ts

More misadventures ensue, their shape depending on the angle of the attempt. This one produces a mysteriously hidden middleware function:

declare module "redux-thunk" {
    import { Middleware } from 'redux'
    export default Middleware
}

This is better. Note I'm not so much programming by this point as word processing:

declare module "redux-thunk" {
    import { Middleware } from 'redux'
    const thunkMiddleware: Middleware
    export default thunkMiddleware
}

My success that it compiles is short lived, as I then try to use the module:

    const thunkAction = getVersionFromServer(fetch);
    store.dispatch(thunkAction);

The compiler complains:

error TS2345: Argument of type '(dispatch: any) => any' is not assignable to parameter of type 'Action'.
  Property 'type' is missing in type '(dispatch: any) => any'.

Aha! @aikoven said all we need to do, basically, is augment the interface!

declare module 'redux' {
    export interface Dispatch<S> {
        <R>(thunkAction: (dispatch: Dispatch<S>) => R): R;
    }
}

That doesn't so much 'augment' it, as entirely mask the official typings:

typings-local/redux-thunk.d.ts(8,14): error TS2305: Module ''redux'' has no exported member 'Middleware'.

There's hours more of this (anyone else best mates with invalid module name in augmentation?)… but eventually you get to the point where it's obvious TypeScript's type system is not saving time. Quite the opposite. So, I hack around it:

    const thunkAction = getVersionFromServer(fetch);
    store.dispatch(thunkAction as any as { type: string });

Slow clap, everyone. Oh, the Java guys will totally leave us alone, now.

You said it's basic, @aikoven. How about writing it down, eh?

@Igorbek
Copy link
Contributor

Igorbek commented Jun 3, 2016

That is the definition of thunk's dispatch:

declare module "redux" {
    export interface Dispatch<S> {
        <R>(asyncAction: (dispatch: Dispatch<S>, getState: () => S) => R): R;
    }
}

Let me try to reproduce your issues.

@aikoven
Copy link
Collaborator

aikoven commented Jun 3, 2016

@garthk I'm sorry if my words offended you. I'll add comments to the definition file with clarification on how new dispatch signatures are meant to be added by middleware typings.

@garthk
Copy link

garthk commented Jun 4, 2016

No worries, @aikoven: you weren't offensive. I'm sorry I got stroppy. I tried to edit it down, but must have failed.

If you could document how to add new dispatch signatures, though, I'd find it very handy.

@aikoven
Copy link
Collaborator

aikoven commented Jun 9, 2016

See reduxjs/redux-thunk#77

@timdorr
Copy link
Member

timdorr commented Sep 27, 2016

So, this looks to be more on the side of the middleware libraries. It looks like redux-thunk got typings added. Other libraries that need typings should have issues/PRs opened up on their respective repos. Thanks for the investigation on this!

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

No branches or pull requests

5 participants