What is the best way to cast the action
parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a base interface with a property type. The extended action interfaces can have more properties that are all different between the action interfaces. Here is an example below:
interface IAction {
type: string
}
interface IActionA extends IAction {
a: string
}
interface IActionB extends IAction {
b: string
}
const reducer = (action: IAction) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a) // property 'a' does not exists on type IAction
case 'b':
return console.info('action b: ', action.b) // property 'b' does not exists on type IAction
}
}
The problem is that action
needs to be cast as a type that has access to both IActionA
and IActionB
so the reducer can use both action.a
and action.a
without throwing an error.
I have several ideas how to work around this issue:
- Cast
action
toany
. - Use optional interface members.
example:
interface IAction {
type: string
a?: string
b?: string
}
- Use different reducers for every action type.
What is the best way to organize Action/Reducers in typescript? Thank you in advance!
Here is how I do it:
IAction.ts
UserAction.ts
UserReducer.ts
IUserReducerState.ts
UserSaga.ts
UserService.ts
https://github.com/codeBelt/typescript-hapi-react-hot-loader-example
If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:
You can learn more on section "Type Guards and Differentiating Types" of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html
Here's the approach I've taken for this problem:
I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.
Two parts of the problem
Several comments above have mentioned concept/function `actionCreator´ - take a look at redux-actions package (and corresponding TypeScript definitions), that solves first part of the problem: creating action creator functions that have TypeScript type information specifying action payload type.
Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner (as the question was asked about TypeScript).
The solution
Combine redux-actions and redux-actions-ts-reducer packages:
1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:
2) Create reducer with initial state and reducer functions for all related actions:
As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred (so this even works with
noImplicitAny
TypeScript compiler option)If You use actions from some framework that doesn't expose
redux-action
action creators (and You don't want to create them Yourself either) or have legacy code that uses strings constants for action types you could add reducers for them as well:so it is easy to get started without refactoring Your codebase.
Dispatching actions
You can dispatch actions even without
redux
like this:but dispatching action with
redux
is simpler - use thedispatch(...)
function as usual:Lately I have been using this approach:
This here:
ensures that the
Action
s are all plain objects. Now you can make actions like this:const action = new BeginBusyAction()
. (yay \o/)To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)