Reputation: 2113
I have an app which has multiple stores with the same reducer functionality. I tried to make a generic reducer and it worked fine.
generic reducer settings:
interface State<T> {
itemList : T[]
}
const initialState: State<any> = {
itemList: []
}
const createReducer = <T>(type: string) => {
return <T>(state = initialState, action: any): State<T> => {
switch (action.type) {
case types.get(type).Add:
return {
...state,
itemList: [...state.itemList, action.payload]
}
case types.get(type).AddList:
return {
...state,
itemList: [...state.itemList, ...action.payload]
};
default:
return state;
}
}
}
Then I would combine reducers as follows:
export const reducers: ActionReducerMap<AppState> = {
vehiculeState: createReducer<Vehicule>('vehicule'),
rentState: createReducer<Rent>('rent'),
clientState : createReducer<Client>('client'),
companionState : createReducer<Client>('companion'),
paymentState : createReducer<Payment>('payment'),
notificationState : createReducer<Notification>('notification'),
employeeState : createReducer<Employee>('employee')
}
The issue is that by switching to generic mode I would have to rewrite a big chunk of my app, since I have already created multiple reducers with states that contains properties named (clientList
, vehiculeList
,...), and the property name itemList
is not very informative. So my question is how to switch to generic mode and keep the state properties as they are?
An example of a current reducers:
export function rentReducer(state = initialState, action: rentActions.RentActions): State {
switch(action.type){
case rentActions.ADD_RENT:
return{
...state,
rentList : [...state.rentList, action.payload]
}
case rentActions.ADD_RENT_LIST:
return {
...state,
rentList : [...state.rentList, ...action.payload]
};
default:
return state;
}
}
Upvotes: 2
Views: 2575
Reputation: 250256
You could use a string to represent the itemList
property name, and use mapped types to turn that string into a type safe property for the State
type. One disadvantage is that using the spread operator is not supported for mapped types, but we can get a similar effect with Object.assign
// We add an extra parameter to State that will be the property name passed in as a string literal type
// So State<Vehicle, 'vehicleList'> will be a type equivalent to { vehicleList : Vehicle[] }
type State<T, TListName extends string> = {
[P in TListName] : T[]
}
// We create a function that creates the initial state by initializing an object with an empty array and the given property name
const initialState = <T, TListName extends string>(itemListName: TListName): State<T, TListName> => {
let result = {} as State<any, TListName>;
result[itemListName] = [];
return result;
};
// Since we can't use the spread operator, we create a new function that updates the state
// state will be the original state,
// itemListName the property name which contains the list
// newItems will be the new list
const updateState = <T, TListName extends string>(args: { state: State<T, TListName>, itemListName: TListName, newItems: T[] }): State<T, TListName> => {
return Object.assign({},args.state, {
[args.itemListName] : args.newItems
});
}
// We will use a 2 function approach for the createReducer function
// We do this in order to be able to specify the item type (T) explicitly,
// but not have to specify the string literal type TListName and let it be inferred
const createReducer = <T>(type: string) => <TListName extends string>(itemListName: TListName) => {
return (state = initialState<T, TListName>(itemListName), action: any): State<T, TListName> => {
switch (action.type) {
case types.get(type).Add:
return updateState({
state,
itemListName,
newItems: [...state[itemListName], <T>action.payload]
});
case types.get(type).AddList:
return updateState({
state,
itemListName,
newItems: [...state[itemListName], ...<T[]>action.payload]
})
default:
return state;
}
}
}
export const reducers = {
vehiculeState: createReducer<Vehicule>('vehicule')('vehiculeItems'),
rentState: createReducer<Rent>('rent')('rentItems'),
clientState : createReducer<Client>('client')('clientItems'),
companionState : createReducer<Client>('companion')('companionItems'),
paymentState : createReducer<Payment>('payment')('paymentItems'),
notificationState : createReducer<Notification>('notification')('notificationItems'),
employeeState : createReducer<Employee>('employee')('employeeItems'),
}
Upvotes: 7