Recently I read articles about better static type support in Redux using TypeScript utility, which can dynamically create new types based on declared types. These are pretty useful way to improve DX, mitigate the pain of Redux boilerplate and excessive type definitions.
From implementation ➟ type
The main part is how to type the action object used in reducers, based on the what's returned from action creators. Let's consider basket scenarios which normally have actions to manipulate product items in it.
const BasketActions = {
addItem: (item: Product, quantity: number) => {
return {
type: "BASKET:ADD_ITEM",
payload: { item, quantity },
// to demo how each action can be properly inferred in reducer 'case'
extraProp: {key: 1}
} as const;
},
removeItem: (productId: number) => {
return {
type: "BASKET:REMOVE_ITEM",
payload: { productId }
} as const;
}
};
Is there a way to type the 'returned' object without manually defining them? As we've already had the action object in each action creator function. Essentially use implementation to infer its returned type as discussed here
type AddItem = {type: 'BASKET:ADD_ITEM', payload: {item: Product, quantity: number}}
type RemoveItem = {type: 'BASKET:REMOVE_ITEM', payload: {productId: string}}
export type valueof<T> = T[keyof T];
// generics take action creator (e.g type of BasketActions) as parameter
export type ActionType<TActions extends { [k: string]: any }> = ReturnType<
valueof<TActions>
>;
// type def
type BaskeActionType = ActionType<typeof BasketActions>
export type AppActionTypes = BasketActionType | OtherActionType | ...
Let's break it down to see how the action object is inferred:
valueof<TActions>
This essentially extracts each function from step 1 type: (item: Product, quantity) => {type: 'BASKET:ADD_ITEM', payload: {item: product}, extraProps: {key: number}} | (productId) => {...}
ReturnType<step2 type>
This finally returns object shape from each function type of step 2
AppActionTypes is a new type which is union of all action objects from each action creator's functions.
Happy auth-completion in Reducer
We can see it's more efficient and reliable to write in reducer now:
All the action.type string const can be auto filled when writing each case. Also the action object is properly inferred in each case. They don't even need to have the same shape
Recently I read articles about better static type support in Redux using TypeScript utility, which can dynamically create new types based on declared types. These are pretty useful way to improve DX, mitigate the pain of Redux boilerplate and excessive type definitions.
From implementation ➟ type
The main part is how to type the
action
object used in reducers, based on the what's returned from action creators. Let's consider basket scenarios which normally have actions to manipulate product items in it.Is there a way to type the 'returned' object without manually defining them? As we've already had the action object in each action creator function. Essentially use implementation to infer its returned type as discussed here
ReturnType comes in handy
Let's break it down to see how the action object is inferred:
typeof BasketActions
The typeof operator returns{'addItem': (item: Product, quantity: number) => {..}, 'removeItem': (productId: number) => {...} }
valueof<TActions>
This essentially extracts each function from step 1 type:(item: Product, quantity) => {type: 'BASKET:ADD_ITEM', payload: {item: product}, extraProps: {key: number}} | (productId) => {...}
ReturnType<step2 type>
This finally returns object shape from each function type of step 2AppActionTypes
is a new type which is union of all action objects from each action creator's functions.Happy auth-completion in Reducer
We can see it's more efficient and reliable to write in reducer now:
All the
action.type
string const can be auto filled when writing each case. Also the action object is properly inferred in each case. They don't even need to have the same shapeThe full 'Basket' example can be found here