-
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
Discussion: improving the developer experience for TypeScript users by making changes in the declaration file and adding documentation for types #231
Comments
The last release of redux-thunk almost a year ago mentions dropping TS entirely, placing it in DefinitelyTyped. Is there an update on that? Regardless, I think that yes, we need to coalesce meaningful documentation and examples in one sensible location. I've begun dumping examples (like this one: #224 (comment)) throughout PRs, issues, etc. but having a sole, "here's how you use redux-thunk with TypeScript and here's some 'recipes' " document would be fantastic. I'm happy to contribute some time to this. |
Struggled with the same for an entire day. I have to say that I will move to another library next project I'll set up. It's a pity how simple and great is and because of the combination with TS everything is messed up |
What do you guys think of renaming the generics to be slightly more descriptive? I know it's a minor change, but I guess every little bit helps. |
+1 for the I'd also recommend:
|
Just chiming in to say that I am relatively new to both Redux-Thunk and even newer to TypeScript. Today I set out to upgrade all my thunk actions to use proper typescript, and I came to the issues section of this library to see if there was anything related to I also agree with @agwells that some things aren't very clear at first (e.g. what's the point of my $0.02! |
I'm getting weird error while importing
and
error:
|
One of the first type errors I ran into was:
which gave me:
This is because
~~I think the see Tim's comment here and if you need to derive the type of your store, see my comment here |
@pfgray You should be using Having said that, I would strongly support having the generic type definition names expanded into full names, because at first it is very confusing. I would think, that someone coming into TypeScript without any prior typed language experience will first really run into generics by using Also, official documentation on the preferred way to use typings would be very nice as well. There are a lot of conflicting tutorials out in the public, and from my experience they all fall short in really explaining how all the pieces fit together. If anyone else is struggling with typing their async actions, I suggest to look into here for the time being, this has helped me a lot: https://github.com/reduxjs/redux-thunk/blob/master/test/typescript.ts [2019-04-25] edit: corrected badly referencing |
@joncys , The The only way to do this currently (without the aforementioned augmentation) is to typecast the dispatch function, which is unsafe: (<ThunkDispatch<any, any, any>> myStore.dispatch)(myAction) |
@pfgray You add thunks to a store with middleware, which you can use to define the types for ThunkMiddleware at that time. Those carry through to the Dispatch type, which gets changed to a ThunkDispatch: https://github.com/reduxjs/redux-thunk/blob/master/index.d.ts#L29 Here's what you do when you're applyMiddleware-ing: https://github.com/reduxjs/redux-thunk/blob/master/test/typescript.ts#L30 |
I've put together a pull request that provides more informative names for the type parameters, and adds docblocks above the types, explaining what part of Redux-Thunk they represent. It should be a 100% non-breaking change, because it only changes comments and type param names. :) |
Ok, that takes care of the first part. Who wants to help with documentation? |
@timdorr thanks for explaining that! I tried this in my app, but then realized I'm deriving the state type from my store, after it's created (since my root reducer is often a big nest of // can't use DerivedAppState yet...
const store = createStore(reducer, applyMiddleware(thunk as ThunkMiddleware<DerivedAppState, AppAction>))
type DerivedAppState = typeof store.getState I've talked to a bunch of people who use this pattern, so I think this should be covered in the docs... One technique would be to instead derive state from the first argument to the root reducer: type AppAction = {type: 'foo'}
// this could come from a deep nesting of combineReducers
const rootReducer: Reducer<{user: string}, Action> = (s = {user: "bob"}) => s
type First<T> = T extends Array<infer U> ? T[0] : never
// AppState is now derived from the parameters of the reducer
type DerivedAppState = First<Parameters<typeof rootReducer>>
const store = createStore(rootReducer, applyMiddleware(thunk as ThunkMiddleware<DerivedAppState, AppAction>)) |
@pfgray I do something similar, but I use export const rootReducer = combineReducers(/* each duck's reducers */);
export type FullState = Exclude<ReturnType<typeof rootReducer>, undefined>;
// Intersection of actions from each duck
export type AllActions = FooAction | BarAction;
// I inject my i18n manager as an extra argument to thunks
export type ExtraThunkArg = {
i18n: I18n;
};
export function makeStore(i18n: I18n, initialState?: FullState) {
// The generic `createStore()` type correctly infers everything, including the actions and the thunk
// thunk dispatch(), from the supplied arguments.
return createStore(
rootReducer,
initialState,
applyMiddleware(
thunk.withExtraArgument({ i18n }),
)
);
}
// The type of my store (I sometimes need to reference this in things like unit tests)
export type StandardStore = ReturnType<typeof makeStore>;
// The type of my dispatch() method (for use in places like `mapDispatchToProps()`
export type StandardDispatch = StandardStore['dispatch'];
// The return type of a thunk action creator
export type StandardThunk<TReturnType = any> = ThunkAction<
// In our project, we make all our thunks asynchronous.
Promise<TReturnType>,
FullState,
ExtraThunkArg,
AllActions
>;
// An example of a thunk action creator
export function createThing(): StandardThunk {
// You don't need to implicitly type the params of the inner function, because TS
// correctly infers them from the outer function's return type of `StandardThunk`
return async function(dispatch, getState, {i18n}) {
// do some stuff
};
}
// Or if you prefer arrow methods...
export const createThing = (): StandardThunk => async (
dispatch,
getState,
{ i18n }
) => {
// do some stuff
}; Now, this is a bit of simplification because I actually use some additional middlewares, one of which seems to interfere with /**
* The type of our store's dispatch method.
*/
export type StandardDispatch = ThunkDispatch<
FullState,
ExtraThunkArg,
AllActions
>;
// in `makeStore()`
return createStore<
FullState,
AllActions,
{ dispatch: StandardDispatch },
{}
>( // ...etc |
A few of us have been working on react-redux hooks typings in DefinitelyTyped/DefinitelyTyped#34913 (comment), and were curious about how much thunk support we should build into that. For some things, we could use Redux types and extend those types in redux-thunk, but beyond the simple The existing typings for Opinions welcome on the best way to handle this, especially in a way that's friendly to other middleware without being overcomplicated to set up. |
I don't see why React Redux typings would need to concern itself with a specific middleware. I mean, go right ahead if it's small in scope, but it seems like you'd be missing an opportunity to make any middleware easy to use with your types. |
Really good point. I've been trying to make this work out of the box with thunk while allowing other middleware libraries to add overloads, but I'll see if I can tie everything back to Redux's |
So, uh... I'm trying to use TS with Redux for the first time, and I'm suddenly agreeing that the single-letter generics here are ridiculous. (Not that I disagreed before, I just hadn't tried to make use of them myself.) Where does the discussion in this thread actually stand atm? |
Those were already changed in #243, just not yet released. |
I've created a small repo at https://github.com/threehams/react-redux-thunk-typings to experiment with the interactions between redux, redux-thunk, and react-redux. These (of course) aren't the only libraries, but getting these three working together with minimal setup should help a lot. Tests for all three can be run with I'll see about getting a better PR than the one first tried: This brings up a question, though - how should you use these typings in an ideal world? Should we assume that redux-thunk is included in the store if it's in package.json? This is the most convenient for use with bindings like
If we don't do this, you'd need to set up each of the overloads yourself for each library, which ties into... What about documenting how to extend these libraries to provide types to each usage of If you're not using bindings, and using Few things I'm thinking about right now, and will be adding to the repo. Having separate packages makes this more challenging than I'd wish. |
Hi @timdorr, is there anything missing for a new release with the updated types? In #246 you said, you're waiting for the new types, but as I understand, the new types are now merged in #243? Thanks for clarification and thanks to all contributing to the beautiful new types and docs!!! ❤️ |
I've just started with TypeScript and stumbled upon the following two errors thrown by the TSC:
Is this discussion relevant to the above two. Has anyone been able to solve for these ? I am on redux-thunk 2.2.9 and redux 3.6.0 |
How long am I going to be waiting for you guys to get this fixed? |
@inunotaisho26 : as long as it takes for us to get around to dealing with it. Comments like that don't help. We're currently focusing on rewriting Redux itself in TypeScript. That may impact thunks. I know Tim has also said he isn't happy with the current state of types here. So, please be patient. |
@markerikson You're referencing the wrong person. |
Yep, that's what I get for trying to reply on mobile. Point still stands, though. |
Great to hear this. Looking forward to the future of Redux! Is there anywhere I can follow the progress being made in the transition to TypeScript? |
Ditto |
See reduxjs/redux#3500 and reduxjs/redux#3536 |
It's not quite clear to me, having read this thread. Are there known issues with typings for I won't get into a detailed example in this thread as it's leaning off topic. But it sounds like some of you are saying that this mixture of technologies doesn't really work right yet. |
for those googling their errors (e.g.
and if you can, pitch in on the |
I have a few minimalist helper/alias that have been working well for me so far.
|
@nsmithdev Aha! Custom typing the |
You can also keep your actions as-is and instead type // your custom typings
type ThunkDispatch = {
<TAction>(action: TAction):
TAction extends (...args: any[]) => infer TResult ? TResult :
TAction,
};
// in your component:
const dispatch = useDispatch<ThunkDispatch>(); Important: calling dispatch will infer both normal action creators and thunk-action creators. But you have to be aware of ts type-widening, when you pass literal values into the dispatch, for example But normally you should use action creators instead of literal values: If you want to use literal anyway, you need to pass it like this: This does not make any expectations on the allowed actions, etc. since the hook uses context and its opaque, it has to be defined manually, what the dispatch should be allowed to dispatch. So you can make your app's local // your custom typings
type ThunkDispatch<TAction> = {
(action: TAction):
TAction extends (...args: any[]) => infer TResult ? TResult :
TAction,
};
// in your component:
const dispatch = useDispatch<ThunkDispatch<AppActions>>();
// you can also alias
// type AppThunkDispatch = ThunkDispatch<AppActions>
// const dispatch = useDispatch<AppThunkDispatch>(); I guess the example @nsmithdev provided can be simplified using the infer syntax, if you also want to swap the |
The latest version has new type annotations, but they haven't been released into an NPM package yet. Can someone make that happen soon? |
This has been done, so I'm going to close it out. |
and 2.4.0 is now live! |
I think it's clear that Redux Thunk doesn't provide the best experience for TypeScript developers. A Google search for “redux thunk typescript” yields many results with conflicting and outdated information with different ways to do the same thing. As a TypeScript developer, trying to enter this space can become frustrating very quickly. This is why I'd like to propose some changes we can make to improve this situation.
Breaking the single-letter naming convention for generic types
The most used naming convention for generics in the TypeScript community is using single-letters for type names. It's also what's used in the official TypeScript documentation. While this can work well for simple components that don't require much explanation, it can become quite tedious to work with as the component you're working with becomes more complex.
Take this type, for example.
Without actually looking at the inside code of the type, the only variable type I could guess was
S
(which is the store state). Only when you start looking at the code, it becomes apparent thatR
is the result type by looking at which type the function returns,A
is anAction
by looking at what it's extending and thatE
is the extra argument by looking at its property name. This also means that if you're using an IDE, the parameter (or generic) info become virtually useless as you'll have to look at the definition anyway because it only works with descriptive names.This is why I think we should consider using one of these naming conventions:
or
I personally prefer the second one, as I've seen other codebases use it before and it also prevents you from having to write
ActionType
, which could conflict with another type you might have in a codebase using Redux.The main downside of changing the naming convention is that we would be breaking the convention that's used in most TypeScript codebases as well as in official documentation. Ironically, I think it would improve readability.
Writing documentation on how to use the types with real world examples
There are 3 components in the current declaration file:
ThunkDispatch
,ThunkAction
andThunkMiddleware
. While these names are fairly descriptive and self-explanatory, I think new Redux-TypeScript users would benefit greatly from official documentation with some good examples. At times, it can be difficult to use all the types correctly in conjunction with each other due to the way Redux is designed. This documentation could fit well into a separate TypeScript section on the official Redux documentation webpage, or we could keep it simple without going too much in depth and just put comments into the declarations file. The problem is that people might not always look at the declarations file for documentation.I'm interested to see what everyone thinks of these two proposals, and what else we could do to help the TypeScript community.
The text was updated successfully, but these errors were encountered: