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
@pfgray See https://github.com/reactjs/redux/pull/1537
Basically you just augment Dispatch interface whenever middleware adds support for dispatching new type of things.
@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 reduxtypings install npm~redux-thunkimport 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-thunktypings install --global dt~redux-thunkThis 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-thunkcat > typings-local/redux-thunk.d.tsMore 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?
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.
@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.
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.
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!
Most helpful comment
@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
reduxsupplies typings and refers toredux-thunkin 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 reduxtypings install npm~redux-thunkResult:
error TS2304: Cannot find name 'thunkMiddleware'in line 2. Looking intypings/modules/redux-thunk/index.d.ts, it turns out thesourceisandrew-w-ross/typings-redux, which the author deprecated in favour of the officialreduxtypings. Which, sadly, don't includeredux-thunk. Perhaps the othertypings search redux-thunkresult works:typings remove redux-thunktypings install --global dt~redux-thunkThis one comes from
gaearon/redux-thunkviaDefinitelyTyped. Success?Aah, it'll only work if you use the global
DefinitelyTypedtypings forredux. Damn.@HenrikBechmann offers, in DefinitelyTyped/DefinitelyTyped#6231:
Oh, that'll be easy to slot in, we lie to ourselves after the misadventures to date.
typings remove --global redux-thunkcat > typings-local/redux-thunk.d.tsMore misadventures ensue, their shape depending on the angle of the attempt. This one produces a mysteriously hidden middleware function:
This is better. Note I'm not so much _programming_ by this point as _word processing_:
My success that it compiles is short lived, as I then try to _use_ the module:
The compiler complains:
Aha! @aikoven said all we need to do, _basically_, is augment the interface!
That doesn't so much 'augment' it, as entirely mask the official typings:
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: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?