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
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).
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:
import { createAction } from 'redux-actions';
const negate = createAction('NEGATE'); // action without payload
const add = createAction('ADD'); // action with payload type `number`
2) Create reducer with initial state and reducer functions for all related actions:
import { ReducerFactory } from 'redux-actions-ts-reducer';
// type of the state - not strictly needed, you could inline it as object for initial state
class SampleState {
count = 0;
}
// creating reducer that combines several reducer functions
const reducer = new ReducerFactory(new SampleState())
// `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
// Type of `action.payload` is inferred based on first argument (action creator)
.addReducer(add, (state, action) => {
return {
...state,
count: state.count + action.payload,
};
})
// no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
.addReducer(negate, (state) => {
return {
...state,
count: state.count * -1,
};
})
// chain as many reducer functions as you like with arbitrary payload types
...
// Finally call this method, to create a reducer:
.toReducer();
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:
const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';
const reducer = new ReducerFactory(new SampleState())
...
// when adding reducer for action using string actionType
// You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
.addReducer(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
return {
...state,
message: action.payload,
};
})
// action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
.addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
return new SampleState();
})
...
.toReducer();
so it is easy to get started without refactoring Your codebase.
You can dispatch actions even without redux
like this:
const newState = reducer(previousState, add(5));
but dispatching action with redux
is simpler - use the dispatch(...)
function as usual:
dispatch(add(5));
dispatch(negate());
dispatch({ // dispatching action without actionCreator
type: SOME_LIB_STRING_ACTION_TYPE,
payload: newMessage,
});
Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.