-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Inferred dispatch type is not helpful #2896
Comments
This is intentional. We do not find it useful to try to limit what types of actions can be dispatched at the TS level. Any reducer can respond to any action, and any action can be dispatched without a single reducer caring about it - that's an intentional part of Redux's design. Because of that, we specifically recommend against the semi-common practice of trying to create TS unions representing action types: https://redux.js.org/usage/usage-with-typescript#avoid-action-type-unions The main goal of inferring an Is there a specific reason you're trying to limit things at the TS level? |
Adding to that: every middleware you have in your application can send any kind of action to your reducers - and many of them also will. There is no real value in limiting the action type at dispatch. Just use an action creator and don't dispatch hand-written actions and you should be pretty much as safe as it makes sense. |
Right, if you think of https://github.com/eduter/redux-toolkit-example/blob/39b4e0d/src/features/counter/Counter.tsx#L34 In past projects I didn't have this problem because I was writing all the boilerplate by hand. When writing my own action creators, they would only use the parameters they expected, while the ones generated by the toolkit will put anything they get (including mouse events) into the payload property. I guess it's a fair price to pay for all the code it saves me from writing. |
Well, let's go through these errors: dispatch(() => 'not an action')} in this case, onClick={increment} // A non-serializable value was detected in an action, in the path: `payload`. This one is... TypeScript being TypeScript - and overloads. The method signature of the action creator here is I wonder if we could catch that, but it would not be in the type of |
@markerikson I think we could catch that second case with the change export interface ActionCreatorWithoutPayload<T extends string = string>
extends BaseActionCreator<undefined, T> {
/**
* Calling this {@link redux#ActionCreator} will
* return a {@link PayloadAction} of type `T` with a payload of `undefined`
*/
- (): PayloadAction<undefined, T>
+ (noArgument: void): PayloadAction<undefined, T>
} |
sure, seems reasonable to get into 1.9.1! |
That @phryneas is a computer genius man! (classical reference) |
Hey guys, I'm trying to understand the change that was done here with that I see that exact error @markerikson attached as a screenshot but I personally don't understand why it is something good or helpful. What's the problem with the mouse event being passed into payload here if the action doesn't use it (so it'll be ignored) ? In many cases I just want a component to call whatever I pass to it like <Dialog onClose={reduxCloseHandlerWithDispatchAttached} /> And because of that change with noArgument: void I have to make dummy wrappers like <Dialog onClose={() => reduxCloseHandlerWithDispatchAttached()} /> Just to prevent that mouse event being passed to the action that doesn't even care about it. From reading this thread I understood it is a trade off to avoid
but is non-serializable value even a problem if it doesn't end up in the state? |
@maksnester actions also need to be serializable, to allow proper time travel debugging. actions are recorded to allow the devtools to support skipping and reordering them. |
I’m only beginning to learn about redux and RTK and was also surprised that I was able to dispatch actions which are not supported by the store. Is there any resource I could learn about why being able to do that in my application code was desirable? Thanks! |
@adrian-gierakowski : conceptually, you can think of dispatching actions like an event emitter. You could do |
Thank you for your reply @markerikson! I still have trouble understanding why one would want to dispatch an action for which there is no registered handler in the store. Could you give me a real life example of such situation? Do stores\reducers tend to be modified on the fly with handlers registered and de-registered at runtime. Even in such situation, I think we should be able to know, at build (type checking) time, the set of all possible handlers which might become registered at some point during lifetime of the application. |
@adrian-gierakowski it's important to remember that you aren't the only one dispatching to your store. Any middleware you add has the ability to dispatch actions, and Redux itself has internal actions it will dispatch - INIT being the obvious one, which is actually randomised to prevent reducers from handling it as a special case. |
@adrian-gierakowski there will always be at least one action in dev that cannot be part of your types, and that's the Redux store's As a result, you can never rely on the completeness of a hand-written action type - and at the same time, even maintaining the "almost complete" type by hand is a lot of work. Since it's not complete, you cannot use it in reducers, as you will end up with reducer functions that have a "too tight" type signature. And on the side of dispatch, you either put in a lot of work into that "almost complete" type, or you make the pragmatic choice to just never dispatch hand-written actions, but instead always call an action creator. Since you get action creators for free, the decision between "almost no work" and "a lot of work" should be easy in almost all cases. |
thank you both
Ok, but I guess users of redux wouldn't normally dispatch these from their application code? Such special events could be dispatched via an additional, untyped dispatch method
If middlewares are added statically, I don't see why they shouldn't be able to register, at type level, actions which they can handle. If a more dynamic behaviour is required, again, a
As far as I can from a sample app created with Seems to me that in 98% of cases, dispatching an action which has not been registered in the store would be a programming error, and one which could be avoided by better types. I'm not claiming that it would be easy to come up with such types, just that it would be desirable. |
How would that change if you add that action to your store's types - how would you ever find out that none of your reducers actually reacts to it? If you want that guaranteed:
|
Thanks! Would you be able to point me in direction of some resources, docs, or ideally sample\template project from which I could learn more about this approach?
I'm not sure I understand the question, but it's probably due lack of fundamental understanding on my side. In general I'd expect an api for creating composing slices and creating a store to be able to automatically produce a type which accurately reflects the capabilities of the store it produces |
For what it's worth, you could write up a quick middleware to log when state doesn't change as a result of an action being dispatched: const noopMiddleware: Middleware = ({ getState }) => (next) => (action) => {
const stateBefore = getState();
const result = next(action);
const stateAfter = getState();
if (stateBefore === stateAfter) console.log("State did not change when action was dispatched", action);
return result;
} But honestly this seems like a non-issue to me, part of Redux's principles is that reducers should be able to handle any action, and returning the same state is an entirely valid response. |
Yeah - overall it feels like you're looking for a solution to a problem that doesn't exist or is irrelevant :) |
I was hoping that the
AppDispatch
from the examples would only accept the actions I defined, but it accepts basically anything.Was that a design decision, a bug, a known limitation from the current type definition, a limitation from TypeScript itself?
I've created this repo to illustrate what I mean:
https://github.com/eduter/redux-toolkit-example
More specifically these lines:
https://github.com/eduter/redux-toolkit-example/blob/39b4e0d/src/features/counter/Counter.tsx#L48
https://github.com/eduter/redux-toolkit-example/blob/39b4e0d/src/features/counter/Counter.tsx#L26
https://github.com/eduter/redux-toolkit-example/blob/39b4e0d/src/features/counter/Counter.tsx#L34
The text was updated successfully, but these errors were encountered: