-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Typings don't work for redux 4 #169
Comments
After updating redux to v4.0.0-beta.1, compilation results in following error:
My dependencies:
When I change Dispatch definition to following lines from
|
It is not fixed all cases, moreover, you effectively pass Action instead of State to thunk action defined couple lines earlier:
Also I don't know right solution still... Will try to find. PS. Author really need comaintainers here... ((( |
Typings for Redux v4 are not yet final. |
Disclaimer: Typings for Redux v4 are not yet final import { Middleware, Dispatch } from 'redux';
export type ThunkAction<R, S, E, D> = (dispatch: Dispatch<D, S>, getState: () => S, extraArgument: E) => R;
declare module 'redux' {
export interface Dispatch<D, S = {}> {
<R, E>(asyncAction: ThunkAction<R, S, E, D>): R;
}
}
declare const thunk: Middleware & {
withExtraArgument(extraArgument: any): Middleware;
};
export default thunk; Per the definition of |
NOTE: redux-thunk doesn't support redux v4 yet. reduxjs/redux-thunk#169
OK, so I'm just getting into redux-thunk. Just want to make sure I understand this. In Redux 3, my thunk action type currently should look something like this, right? // using third parameter for environment
export type MyThunk = ThunkAction<void, any, "env1" | "env2"> But with Redux 4, it will look (possibly) something like this (stricter typing possible), right? export type MyThunk = ThunkAction<void, MyThunkResultAction, "env1" | "env2"> Which then restricts my If this is the case (which I hope it is), can we do something like this instead: // third type is the thunk's return, is optional, and defaults to void if not specified
export type MyThunk = ThunkAction<MyThunkResultAction, "env1" | "env2", void>
// so you can do this
export type MyThunk = ThunkAction<MyThunkResultAction, "env1" | "env2">
// or possibly this:
export type MyThunk = ThunkAction<MyThunkResultAction, StateType, "env1" | "env2", ReturnType> |
Edit: Looks like it's a constantly moving target at the moment, but I'd say something like this might be the direction to go in to keep the generics in similar order to Redux itself.
Changes the contract a little bit, but then again it's a major version of Redux so that's probably not the end of the world. I don't write Typescript definitions often so feel free to let me know if i'm missing anything. |
Would a contributor be willing to release a beta version of redux-thunk that targets "4.0.0-beta.2" if I raised a pull request? Apart from TypeScript I doubt we'll see any changes that break the thunk middleware functionality (reduxjs/redux#1342) |
@NathanNZ ok, let me try. EDIT: Pretty much given up, as I found it hard to support type-safe |
Just created a PR. Please help review. Thank you guys. |
Redux v4 is out. 🎉 |
So this basically makes the library unusable, as long as this is not fixed... |
I just realized, that all sort of this problems exposed by library that is 14 lines of code ))))) |
Ok guy so sometimes typescript's inference doesn't plain work. Here's the best I could do: redux-thunk/index.d.ts: import {Middleware, Dispatch, Action, Store} from "redux";
export type ThunkAction<A extends Action, S, E, R> = (dispatch: Dispatch<A>, getState: () => S, extraArgument: E) => R;
export interface ThunkExt {
dispatch: <A extends Action, S, E, R>(asyncAction: ThunkAction<A, S, E, R>) => R;
}
declare const thunk: Middleware<ThunkExt> & {
withExtraArgument(extraArgument: any): Middleware<ThunkExt>;
};
export default thunk; (original ThunkAction's typings wrong, they have the same template parameter for both the Action typeparam to Redux.Dispatch and the State typeparam for getState()) In store.ts however the dispatchMethod doesn't get picked up unless you explicitly pass in the Ext typeparam to the StoreCreator function, that's why we export it in redux-thunk/index.d.ts. import {createStore} from 'redux'
import thunk from 'redux-thunk'
const store = (fakeReducer, applyMiddleware(thunk))
store.dispatch(
makeASandwichWithSecretSauce('Me') // This however raises an error.
); If we however explicitly pass in the typeparams into the createStore function, it all works: import {createStore} from 'redux'
import thunk, {ThunkExt} from 'redux-thunk'
const store = createStore<IMyState, MyActions, ThunkExt, {}>(fakeReducer, applyMiddleware(thunk))
store.dispatch(
makeASandwichWithSecretSauce('Me') // Now even the return value works (see full file below)
); What I personally did is to type ThunkExt in a different way to "freeze" the State type and Actions type, and forgot about passing it to the Middleware<> type since typescript isn't picking it up there anyways. import {Middleware, Dispatch, Action, Store} from "redux";
export type ThunkAction<A extends Action, S, E, R> = (dispatch: Dispatch<A>, getState: () => S, extraArgument: E) => R;
export interface ThunkExt<A extends Action, S> {
dispatch: <E, R>(asyncAction: ThunkAction<A, S, E, R>) => R;
}
declare const thunk: Middleware & {
withExtraArgument(extraArgument: any): Middleware;
};
export default thunk; Then in my store.ts: const store = createStore<IMyState, MyActions, ThunkExt<MyActions, IMyState>, {}>(fakeReducer, applyMiddleware(thunk))
store.dispatch(
makeASandwichWithSecretSauce('My wife')
).then((makeSandwichAction) => {
assert(makeSandwichAction.type == 'MAKE_SANDWICH')
}); full store.ts: import { createStore, Dispatch, applyMiddleware, Reducer } from 'redux';
import thunk, { ThunkAction, ThunkExt } from 'redux-thunk';
interface IMyState { }
interface IMakeSandwichAction {
type: 'MAKE_SANDWICH'
forPerson: string
secretSauce: Response
}
type MyActions = IMakeSandwichAction
const fakeReducer: Reducer<IMyState, MyActions> = (state, action) => {
if (state) {
return state
}
else {
return {}
}
}
const store = createStore<IMyState, MyActions, ThunkExt<MyActions, IMyState>, {}>(fakeReducer, applyMiddleware(thunk))
function fetchSecretSauce(): Promise<Response> {
return fetch('https://www.google.com/search?q=secret+sauce');
}
function makeASandwich(forPerson: string, secretSauce: Response): IMakeSandwichAction {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce
};
}
function makeASandwichWithSecretSauce(forPerson: string): ThunkAction<MyActions, IMyState, undefined, Promise<IMakeSandwichAction>> {
return function (dispatch: Dispatch<MyActions>) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce))
);
};
}
store.dispatch(
makeASandwichWithSecretSauce('Me') // typescript error
);
store.dispatch(
makeASandwichWithSecretSauce('My wife')
).then((makeSandwichAction) => {
assert(makeSandwichAction.type == 'MAKE_SANDWICH')
}); Another alternative: const store: Store<IMyState, MyActions> & ThunkExt<MyActions, IMyState> = createStore(fakeReducer, applyMiddleware(thunk)) Hope this is useful for anyone and I might be onto something using redux's built in types for store enhancing which seem correct but typescript isn't picking up all types when it should. |
Another simple hack that doesn't require messing with messy types or declaration files, I had to use it in order to be able to use react-redux's dispatch function which doesn't have an extension API built in: However note that your thunk cannot dispatch itself export type ThunkAction<A extends Action, S, E, R> =
(dispatch: Dispatch<A>, getState: () => S, extraArgument: E) => R;
export type FetchBooksThunk =
ThunkAction<MyAction, IMyStore, undefined, void> & IFetchBooksThunk
interface IFetchBooksThunk {
type: 'FETCH_BOOKS_THUNK'
}
export type PlainActions = IFetchStart | IFetchComplete | IFetchFailed
// if you do this typescript rightly complains about circular referencing:
// export type PlainActions = IFetchStart | IFetchComplete | IFetchFailed | FetchBooksThunk
const actionify =
<AT, A extends Action, S, E, R>(actionType: AT, thunk: ThunkAction<A,S,E,R>)
:Action<AT> & ThunkAction<A,S,E,R> =>
Object.assign(thunk, {type: actionType})
const fetchCurrentTopic: FetchBooksThunk = actionify<'FETCH_BOOKS_THUNK', PlainActions, IMyStore, undefined, void>('FETCH_BOOKS_THUNK', (dispatch, getState) => {
dispatch(fetchStart())
const { topic } = getState()
fetch(URL + topic)
.then(res => {
return res.json()
})
.then((json: IAPIResponse) => {
if(json.error) {
dispatch(fetchFailed(json.error))
}
else {
dispatch(fetchComplete(json))
}
})
.catch(error => {
dispatch(fetchFailed(error))
})
}) |
Are there any plans to fix this anytime soon? |
@danlugo92 do you think you could make a PR? |
I just made a pull request that fixes the issue while leaving the interface basically intact. Pull request: #196 |
No description provided.
The text was updated successfully, but these errors were encountered: