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

Redux with Typescript getting started documentation only exists with Redux-Toolkit #4187

Closed
untoldone opened this issue Oct 11, 2021 · 9 comments

Comments

@untoldone
Copy link

untoldone commented Oct 11, 2021

What docs page needs to be fixed?

https://redux.js.org/usage/usage-with-typescript

What is the problem?

The current state of the documentation as well as related tutorials, stackoverflows etc seem to leave a gap on how to create a redux store without the use of the strong opinions of Redux-Toolkit. This is made even harder by API references not including mention of Typescript types such as https://redux.js.org/api/createstore. Type definitions also don't really share much in terms of the intent of the options of various functions / helper functions in terms of required use of generics.

What should be changed to fix the problem?

Provide a simple example or documentation alternative to https://redux.js.org/usage/usage-with-typescript which does not depend on Redux-Toolkit. For example, provide a typescript compatible version of the content of https://redux.js.org/usage/configuring-your-store -- It's taken me quite a long time to figure out the complex types being used for createStore to figure out how to adapt these examples to work successfully with typescript.

@markerikson
Copy link
Contributor

markerikson commented Oct 11, 2021

Short version is, RTK is the right way to use Redux+TS together. There's less actual code to write, and RTK is designed to infer as many TS types as possible. Frankly, having seen the before and after, I don't know why anyone would want to write Redux code with vanilla Redux, much less vanilla Redux + JS.

Additionally, from our teaching / docs perspective, today RTK is "Redux". If we could deprecate the underlying APIs entirely and just replace them with configureStore and createSlice we would, but we can't due to the amount of existing code that's out there.

We've been slowly revamping the docs over the last couple years to modernize things, and the content is certainly mixed at this point - there's plenty of pages that still show legacy patterns, just because we haven't gotten around to rewriting them.

I would certainly love to have better ways to integrate TS type definitions into the API docs in all our pages, per reduxjs/redux-toolkit#1046 , but haven't had any time to investigate ideas for this further.

I'll also say that it's really hard to figure out the right way to cover topics when some of your users use plain JS and some use TS. I'd like to have all snippets be toggleable between syntaxes, per reduxjs/redux-toolkit#1203 . Unfortunately, that would take a ton of time and effort. This is an all-volunteer library maintainer team, there's a ton of stuff to do, and we've only got so much time to work on things so we have to prioritize.

But, if you're asking us to provide examples geared towards legacy patterns that we no longer want people using, that's not something we plan to do.

@phryneas
Copy link
Member

phryneas commented Oct 12, 2021

Adding to this:

TypeScript before Redux Toolkit was an afterthought. None of the Redux maintainers at the time (to my knowledge) actively used TS back then and the userbase did what you do in that situation: find a way to make it work.
But vanilla Redux is made in a way that it cannot be made to work easily. It requires lots of ceremony, to create a half-hacky solution that masks some bugs because it makes assertions on types that are not true.
E.g. creating a union action type will be exhaustive, which will mask bugs like missing returns of completely unexpected action shapes from the type system. For more details see this article.

There are ways of writing TypeScript with Redux easily, but all of them require a third party library. https://github.com/piotrwitek/typesafe-actions (use isActionOf, not isOfType with this!) was very popular and I personally enjoyed using https://github.com/aikoven/typescript-fsa for a year or so.

So, the only sane documentation for vanilla Redux + TypeScript would be "use a helper library to make this work better".

That said, there is a helper library. And that helper library is official. And that helper library is even the official recommendation for any Redux code written in the last two years. It it Redux Toolkit.

We really do not want you to write vanilla Redux (which already is a lot of frustrating boilerplate) and then slap double the code on top of this to follow patterns that were established to make something that "kinda does not really work" work. We want you to use Redux Toolkit, which is TypeScript first and removes all that type boilerplate completely.
And that's why we do not have "Vanilla Redux with TypeScript" in the docs any more: doing so is just bad advice.

PS: using createStore with TypeScript should to my knowledge not need a single type definition at all. That's one of the few things that always worked quite well. You can see that in this CodeSandbox

@untoldone
Copy link
Author

untoldone commented Oct 12, 2021

Thanks for the quick and detailed replies here -- they are really useful and hope others going down a similar path to myself find this thread. I do think if I had seen this discussion, it definitely would have influenced my decision significantly.

I have a tendency to reduce the number of dependencies and concepts in new projects to reduce the overhead of onboarding other folks onto new projects. Given my past experience with specifically other helper libraries, I tend to default avoid them until I find specific (hopefully overwhelming) reasons to pull them in... with prior libraries, it feels like I've gotten the kitchen sink of which I use one or two things and with many opinions which are hard to opt out of / many of which I don't end up agreeing with given my circumstances (no experience with redux toolkit ... just explaining why I ended up thinking this / not expressing thoughts on redux toolkit specifically).

I'm not sure how common my behavior is among other developers, but while reading the current documentation, when I'd read something like "We assume that a typical Redux project is using Redux Toolkit and React Redux together" after having seen many other pages which don't do this (such as the "configure your store" page), I assumed a person writing the documentation had this opinion, but possibly not everyone shared this opinion.

One suggestion given my experience yesterday might be to update the page to at least acknowledge the current gaps in the other areas of redux-typescript documentation on the typescript documentation page itself. Another might be to make the statements in the current documentation such as "There are multiple possible approaches to type checking Redux code. This page shows our standard recommended patterns for using Redux and TypeScript together..." to be stronger than a "standard recommended pattern" and to move it to be one of the first sentences in the documentation e.g. something like "To use Redux with Typescript you must use the Redux Toolkit in your project." Given the inconsistency of the documentation currently, something like this will make it much more obvious to someone with preconceptions such as myself that it is stronger than just a recommendation ... even with all the language stating things like its the "standard", I still spent quite a long time looking for alternative paths.

In regard to createStore specifically -- I had been trying to create a store with initial state and enhancers as well. I found that the type specification of specifically the enhancers to be particularly confusing -- ultimately I ended up type asserting rather than being able to figure out the correct set of generics to use with functions such as compose. Note it looks like Redux Toolkit itself does the same here https://github.com/reduxjs/redux-toolkit/blob/8565fc25803beedc1032d67473c6b448da588405/packages/toolkit/src/configureStore.ts#L190

@Methuselah96
Copy link
Member

I'm still on vanilla Redux just because my company adopted Redux before RTK was a thing and we have figured out how to remove most of the boilerplate. I can highly recommend typesafe-actions. In my opinion, that is the way to do Redux + TS without RTK. Our company hopes/plans to switch to RTK at some point, but there hasn't been a compelling reason to make the switch yet.

The types for the enhancers are indeed confusing and hard to get right. In fact, you might find it's impossible to get the types right if you have more than one enhancer (due to #3776).

@audiolion
Copy link

Hey @markerikson ,

Wanted to chime in, I just finished converting all of the reducers and actions to TypeScript using old react redux patterns. I figured out how to extract all the state and types from my store to make a useAppSelector and useAppDispatch hook, but I first looked at the docs and found no help there. I want to move to rtk, but it is unclear if I can take our current compose with enhancers and middleware and swap in rtks create store function and have all hand rolled reducers still and get types. If that is an ok swap could the docs say that? If not, for the people trying to move off legacy code, getting everything into typescript first is a huge benefit to more safely refactor. I wanted docs on how to do types with the redux create store because I was trying to take a small step in the right direction. I am sure there are many legacy redux codebases out there from the flux era and I inherited one at my company. I understand the desire to push people to the new, but a section to help people migrate then could help?

@markerikson
Copy link
Contributor

Extracting the RootState and AppDispatch types should be the same - typeof store.dispatch and ReturnType<typeof store.getState> work the same way regardless of how you created the store.

Reducer definitions are all independent, and you can absolutely mix and match slice reducers defined with createSlice and hand-written logic, both at the JS and TS levels.

I'm open to suggestions/PRs on specific content to include that would be helpful, it's just not anything that I'm going to prioritize working on myself.

@audiolion
Copy link

Thanks for the reply @markerikson !

Extracting the RootState and AppDispatch types should be the same - typeof store.dispatch and ReturnType work the same way regardless of how you created the store.

That was the missing link for me, I ended up using:

const rootReducer = combineReducers(reducers)

export default rootReducer

export type ReduxActions = ActionFromReducersMapObject<typeof reducers>
export type ReduxState = ReturnType<typeof rootReducer>

I think stating that those two uses (typeof store.dispatch and ReturnType<typeof store.getState> work regardless of whether you are using vanilla createStore or RTK createStore would have been sufficient for me. I will see if there is any sort of PR I can make that is minimal but contains this information.

Reducer definitions are all independent, and you can absolutely mix and match slice reducers defined with createSlice and hand-written logic, both at the JS and TS levels.

Ok, that makes sense. RTK does a lot of cool stuff, I just wasn't sure if it was all compatible and didn't see any docs stating that it is a swap-in replacement.

@phryneas
Copy link
Member

phryneas commented Nov 1, 2021

@audiolion regarding the

export type ReduxActions = ActionFromReducersMapObject<typeof reducers>

Please note that we generally recommend against the Action-union-type pattern that was prevalent in the past.
Realistically, the only valid Action type any of your reducers should be typed towards is AnyAction, since every of those reducers can and will be called with any action ever dispatched (including random, un-typable actions), so assuming anything else might end up with your type system hiding potential bugs for you.

Also see Do not create union types with Redux Action Types. It's most likely an antipattern.

@audiolion
Copy link

@phryneas great blog post! And thanks for the tip.

I think in our case since every action is hand-written and small enough to be enumerable, typing it this way, temporarily, allows us to more safely refactor. But is not something we would want to keep.

@markerikson markerikson closed this as not planned Won't fix, can't repro, duplicate, stale Apr 16, 2023
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