Using createSlice is great, I can easily export all my reducer-actions .
But what if I also want to create some plain actions, which just trigger my Sagas and does not act as reducers in current slice?
I can easily do this by provided createAction method, but then the code looks like this (it's not really DRY):
// Saga-related plain actions
export const login = createAction('auth/login')
export const register = createAction('auth/register')
export const confirmDevice = createAction('auth/confirmDevice')
export const loginWithPin = createAction('auth/loginWithPin')
// Regular reducer actions in current slice
export const {
loginSuccess,
setAccount,
} = slice.actions
Is there a way to make it a little more DRY?
I came up with a helper method which I use and then it looks like this:
(It just add the proper prefixes of current slice name and applies 'createAction' method. By no means it's perfect)
export const {
login,
register,
confirmDevice,
loginWithPin,
} = getPlainActions(slice, [
'login',
'register',
'confirmDevice',
'loginWithPin',
])
Code of the helper method looks like this:
const prefixedType = (slice: Slice, actionType: string): string => `${slice.name}/${actionType}`
const getPlainActionPartial = (slice: Slice) => (actionType: string) => {
const actionTypeWithPrefix = prefixedType(slice, actionType)
return createAction(actionTypeWithPrefix)
}
export const getPlainActions = (slice: Slice, actionTypes: string[]): PlainActions => {
const actionTypesObj = {}
const getPlainAction = getPlainActionPartial(slice)
actionTypes.forEach((actionType) => {
actionTypesObj[actionType] = getPlainAction(actionType)
})
return actionTypesObj
}
You could also just add empty reducers to your call to createSlice (which you are calling anyways):
const slice = createSlice({
name: "...",
initialState: "...",
reducers: {
login(){},
register(){},
confirmDevice(){},
loginWithPin(){},
}
});
DRY is nice to a certain point, but every time one adds a DRY abstraction, it intoduces a new concept that has to be learned.
From a "teaching redux-toolkit" perspective, we'd like to add as few new concepts as possible, as redux itself is already a pretty big concept for people to understand.
Honestly, I even have to say I like your initial example with all the createAction calls as it is very easily readable - especially as it is the half amount of lines.
Also, while your example might work for JavaScript, it won't work well for TypeScript, as there's no good way to type the payload types and payload types would move away from the type names.
So this:
export const login = createAction<LoginData>('auth/login')
export const register = createAction<RegistrationData>('auth/register')
export const confirmDevice = createAction<string>('auth/confirmDevice')
export const loginWithPin = createAction<number>('auth/loginWithPin')
would become something like
export const {
login,
register,
confirmDevice,
loginWithPin,
} = getPlainActions<[
LoginData,
RegistrationData,
string,
number
], typeof slice>(slice, [
'login',
'register',
'confirmDevice',
'loginWithPin',
])
That said, there's nothing speaking against using that code for yourself in your project. If it works well for you, it works well for you :)
I just don't think that we'll add any more mental overload to RTK if it doesn't bring serious (like, dozens lines of code for a very common use case) benefits.
Wow, phryneas, thank you for such an amazing reply! I totally agree with you and I'm really grateful you've laid it out to me in such an eye-opening way. I hope other folks using redux-saga will stumble upon this issue and read your explanation. Thanks a ton!
Most helpful comment
You could also just add empty reducers to your call to
createSlice(which you are calling anyways):DRY is nice to a certain point, but every time one adds a DRY abstraction, it intoduces a new concept that has to be learned.
From a "teaching redux-toolkit" perspective, we'd like to add as few new concepts as possible, as redux itself is already a pretty big concept for people to understand.
Honestly, I even have to say I like your initial example with all the
createActioncalls as it is very easily readable - especially as it is the half amount of lines.Also, while your example might work for JavaScript, it won't work well for TypeScript, as there's no good way to type the payload types and payload types would move away from the type names.
So this:
would become something like
That said, there's nothing speaking against using that code for yourself in your project. If it works well for you, it works well for you :)
I just don't think that we'll add any more mental overload to RTK if it doesn't bring serious (like, dozens lines of code for a very common use case) benefits.