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

Facilitate adding types to the dispatch function #1983

Closed
pffigueiredo opened this issue Jan 11, 2023 · 3 comments
Closed

Facilitate adding types to the dispatch function #1983

pffigueiredo opened this issue Jan 11, 2023 · 3 comments

Comments

@pffigueiredo
Copy link

What is the new or updated feature that you are suggesting?

MOTIVATION

Currently, the recommended method for ensuring proper typing of the dispatch function is to create a new hook, named useAppDispatch, that is derived from the existing useDispatch hook. By using this new hook exclusively, you can ensure that only properly typed actions are being passed to the dispatch function.

export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch

To ensure that this approach functions properly in a larger team setting, it may be necessary to implement additional measures to prevent team members from importing the original useDispatch hook directly. One such measure could be to create an import restriction rule using an ESLint plugin such as eslint-plugin-import-helpers, which allows for the creation of custom helpers to restrict imports. By enforcing the use of the derived useAppDispatch hook, it guarantees that all dispatched actions are properly typed.

Not only that, but it seems inelegant that there is already a function that we can't somehow have "automatically typed".

SOLUTION

Since we don't want to be mixing @reduxjs/toolkit and react-redux code, it would be impossible to derive the useDispatch function from the configureStore, which makes achieving a properly typed dispatch function without any consumer effort infeasible.

As such, my purposed solution is to rely on "typescript's module augmentation". This will allow the consumer to extend the Action type provided in useDispatch and have it automatically typed for every dispatch use.

image

The required implementation has ~3 steps:
1- Defining an interface that can be extended by the consumer;
2- Defining a type that parses what was passed into the interface (interfaces can't be represented by a union of members);
3- Use that interface as the useDispatch default generic param type;

The consumer would need to:
1- Augment the empty interface with an array of their store's Action types

After that, the dispatch function should be automatically typed.


Ts Playground of a quick implementation: playground link

Why should this feature be included?

This feature will improve the DX for typing the dispatch function by a lot without any breaking changes and can be done with very little effort.

What docs changes are needed to explain this?

The docs about "Getting the Dispatch type" - the old docs could continue (as they would still be valid), but new ones could be added.

@markerikson
Copy link
Contributor

Hi. Appreciate the detailed writeup, but part of our TS usage philosophy is that we want explicit usage of TS types. Relying on overriding global ambient types is too risky and can have unexpected behavior. This is why we removed the DefaultRootState type in https://github.com/reduxjs/react-redux/releases/tag/v8.0.0 - see the rationale we wrote up in #1879 for background.

@pffigueiredo
Copy link
Author

Hey @markerikson, thanks for the quick response! But I'm not sure I follow. 🤔

You (the consumer) wouldn't be overriding anything at all. This wouldn't also be done in an ambient file, but instead, in a module with the declare module "react-redux"{...} syntax.

Meaning that the consumers wouldn't be able to override it, they would only be able to extend it. So I'm not entirely sure what would fail here.

And this type, would make it just fallback to AnyAction if anything funky was being passed in.

    type CustomerAction = GlobalStoreAction extends Array<infer R extends Action> ? R : AnyAction;    

Anyway, I completely understand if this isn't the direction that the Redux team wants to take, as it adds a bit of an overhead for where the types might be coming from.

@phryneas
Copy link
Member

phryneas commented Jan 11, 2023

The react-redux types had this ability at some point (someone just smuggled it into DefinitelyTyped without our knowledge), and after quite a lot of internal discussions, we removed it.

Global module augmentation is... global. And that is oftentimes bad.

This starts with things like libraries. If we have this in place, it would only be natural that redux-thunk would ship with types that augment Dispatch.
But then you install @reduxjs/toolkit, which has redux-thunk as a dependency (and references its types). Now those types are in there.
Even if you create a store with redux-thunk disabled, the Dispatch type will contain the signature of redux-thunk, which will result in completely incorrect types.
This goes not only for redux-thunk. Probably other libraries like connected-redux-router etc. would ship with types globally augmenting the RootState type. Not good either.
Every one of your dependencies could pollute your store types, no matter if you actually use them or if they are just pulled in as a dependency of a dependency.

What if you have a monorepo, or multiple stores that have different types on server and client or in different configurations for different builds?

As soon as you start augmenting, you decouple the "real runtime store" from the "TypeScript typed store".

Of course, all that said, you can totally still use module augmentation in your project if you want that - but then you need to do that in your code, and you will create hooks for it (which brings you back to your starting point).

You could do

export interface RootState {
  // will be augmented elsewhere
}

export const useAppSelector = TypedSelectorHook<RootState>

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

3 participants