redux-actions has a very nice combineActions helper https://redux-actions.js.org/api/combineactions which allows to reduce multiple distinct actions with the same reducer. It would be nice to have something similar in redux-tookit, so we could easily boil down this:
builder
.addCase(createTodoSuccess, (state, { payload: todo }) => ({
...state,
[todo.id]: todo
}))
.addCase(updateTodoSuccess, (state, { payload: todo }) => ({
...state,
[todo.id]: todo
}))
to this:
builder
.addCases([createTodoSuccess, updateTodoSuccess], (state, { payload: todo }) => ({
...state,
[todo.id]: todo
}))
WDYT?
What would the typings imply if createTodoSuccess and updateTodoSuccess had different payloads? This will lead to all kinds of problems down the road.
Honestly, I'm not really a fan. If you really wanted to do this, you could just define your case reducer as a function in outer scope and call addCase with that function multiple times.
Yeah, it's not an unreasonable suggestion, but I'm inclined to skip it for now. The typings would likely get too weird, and you can handle them by reusing the same reducer.
The typings would likely get too weird
Is that from an internal implementation perspective or for consumers?
If it's for consumers, typesafe-actions addresses it by making the action type provided to the reducer a union of the possible actions. In cases where the payload type is incompatible, you can type guard by checking the action type. Personally it feels tidier than splitting out the reducer function and needing to explicitly type it.
It would massively bloat up the implementation for very little gain. Doing it for the builder notation of extraReducers wouldn't even be so bad by itself. But then people would infer that something similar would have to exist for the builder notation of createReducer. Subsequently for the reducers option of createSlice.
And if you take a look at the typing implementation of createSlice, that one is already very complicated. It actually took months of user feedback until we had every edge case in there.
Adding a builder notation type thing on top of it would at least double that. Or even be impossible. Because in that case, we'd need the return value of that builder chain to evaluate the actions that were added along the way. And we have already encountered countless situations where this leads to circular type references, which is usually solved by having the builder callback return void, which is not possible here.
That cascade of unpleasantries aside: Why not just re-use the same reducer twice?
{
extraReducers: builder => {
function sharedReducer(state: State, action: PayloadAction<SharedPayload>){ /*...*/ }
builder.addCase(actionA, sharedReducer).addCase(actionB, sharedReducer);
}
}
if you have used createAsyncThunk check your naming, You may forgot to change the asyncFunction naming convention
ex: `export const checkShopName = createAsyncThunk('shop/check-name', async (data, { rejectWithValue }) => {
try {
const payload = await ShopService.checkShopName(data);
return payload
} catch (error) {
return rejectWithValue(error.response.data)
}
})
export const createShop = createAsyncThunk('shop/check-name', async (data, { rejectWithValue }) => {
try {
const payload = await ShopService.createShop(data);
return payload
} catch (error) {
return rejectWithValue(error.response.data)
}
})`
in this situation you may get this error
Update: For anyone reading this a year later: This is actually possible today using the builder notation by combining addMatcher and isAnyOf
builder
.addMatcher(isAnyOf (createTodoSuccess, updateTodoSuccess), (state, { payload: todo }) => ({
...state,
[todo.id]: todo
}))
@SaeidEsk I'm a bit confused. Nobody in this whole issue was talking about having any kind of error. This was a feature request.
Update: For anyone reading this a year later: This is actually possible today using the builder notation by combining
addMatcherandisAnyOfbuilder .addMatcher(isAnyOf (createTodoSuccess, updateTodoSuccess), (state, { payload: todo }) => ({ ...state, [todo.id]: todo }))@SaeidEsk I'm a bit confused. Nobody in this whole issue was talking about having any kind of error. This was a feature request.
I was looking today for a solution of this problem and this answer is pretty neat - thanks for sharing! 馃敟
Update: For anyone reading this a year later: This is actually possible today using the builder notation by combining
addMatcherandisAnyOfbuilder .addMatcher(isAnyOf (createTodoSuccess, updateTodoSuccess), (state, { payload: todo }) => ({ ...state, [todo.id]: todo }))@SaeidEsk I'm a bit confused. Nobody in this whole issue was talking about having any kind of error. This was a feature request.
This is the best implementation so far, we can also play with how many actions have to match to run the matcher. Thank you very much
Most helpful comment
It would massively bloat up the implementation for very little gain. Doing it for the builder notation of
extraReducerswouldn't even be so bad by itself. But then people would infer that something similar would have to exist for the builder notation ofcreateReducer. Subsequently for thereducersoption ofcreateSlice.And if you take a look at the typing implementation of createSlice, that one is already very complicated. It actually took months of user feedback until we had every edge case in there.
Adding a builder notation type thing on top of it would at least double that. Or even be impossible. Because in that case, we'd need the return value of that builder chain to evaluate the actions that were added along the way. And we have already encountered countless situations where this leads to circular type references, which is usually solved by having the builder callback return
void, which is not possible here.That cascade of unpleasantries aside: Why not just re-use the same reducer twice?