'Using generic parameter in Redux Toolkit createSlice reducer with prepare?
I'm trying to create a reducer in RTK that takes two arguments which share a generic type. However it seems impossible to share the generic type between the reducer and prepare functions.
Here is an example of what I'm trying to achieve
type SetPayload<T> = {
thingToBeSet: MyClass<T>;
value: T;
}
export const mySlice = createSlice({
name: 'mySlice',
initialState,
reducers: {
myReducer: {
reducer: (state, action: PayloadAction<SetPayload<any>>) => {
// Modify state here
},
prepare: <T>(thingToBeSet: MyClass<T>, value: T) => {
return {
payload: { thingToBeSet, value }
}
}
}
}
});
As shown in the code above, my prepare function uses a generic parameter (as the type of some properties on thingToBeSet must match the type of value). However, this type parameter cannot be used for the type of action in the reducer function - I tried setting this to any which gives this error:
The types of 'payload.thingToBeSet' are incompatible between these types.
Type 'MyClass<never>' is not assignable to type 'MyClass<any>'.
Type 'any' is not assignable to type 'never'
As any cannot be converted to T - But how can I share T between both the reducer and prepare functions here?
What I think I want is to somehow create a generic object literal for the myReducer object, something like this (unfortunately not valid TS - is this possible?):
myReducer: <T> {
reducer: (state, action: PayloadAction<SetPayload<T>>) => {
// Modify state here
},
prepare: (thingToBeSet: MyClass<T>, value: T) => {
return {
payload: { thingToBeSet, value }
}
}
}
Any help will be greatly appreciated.
Solution 1:[1]
Unfortunately that is not possible. Those types are mapped internally to create a new action creator function - and mapped types cannot inherit generic functionality.
The best you could do would be to cast that action generator to your generic function type before you export it.
export const myReducer = slice.actions.myReducer as <T>(thingToBeSet: MyClass<T>, value: T) => PayloadAction<SetPayload<T>>)
Solution 2:[2]
The eventual solution I used for this was to create the action manually outside of createSlice, and use extraReducers property for the reducer. Setting the type property on the function allows for automatic typing and similar usage to RTK. This is more verbose and not ideal, but it at least gives the correct typing, and implicit types in the reducer.
Action:
const myActionName = 'myAction';
export function myAction<T>(thingToBeSet: MyClass<T>, value: T): PayloadAction<SetPayload<T>> {
return {
type: myActionName,
payload: { thingToBeSet, value }
}
}
myAction.type = myActionName;
Reducer:
createSlice({
...
extraReducers: (builder) => {
builder
.addCase(myAction, (state, action) => {
// Perform state changes
// `state` and `action` have correct implicit types
// (`action` is of type `PayloadAction<SetPayload<unknown>>`)
})
}
});
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | phry |
| Solution 2 | Matthew Winfield |
