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); } }
maxmarinich reacted with confused emoji
xmd5a, k-sav, oldo, brandonvilla21, and Odme reacted with rocket emoji
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.
ravshansbox, Alina-sul, and jlimadev reacted with hooray emoji
turbobeast, zob360, molokovev, oldo, biancadragomir, marconi1992, ravshansbox, JacksonReynolds, alefduarte, codigoisaac, and 2 more reacted with heart emoji
biancadragomir, ravshansbox, Idanatiya, jlimadev, RawHeatG, uvarov-frontend, and dimcheify-prog reacted with rocket emoji
Update: For anyone reading this a year later: This is actually possible today using the builder notation by combining
addMatcher
andisAnyOf
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.
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
addMatcher
andisAnyOf
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.
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
builder .addMatcher(isAnyOf (createTodoSuccess, updateTodoSuccess), (state, { payload: todo }) => ({ ...state, [todo.id]: todo }))
Don’t suppose there’s a way to do this with PURGE
from redux-persist
? The isAnyOf
complains that PURGE
isn’t a Matcher
.
import { createAsyncThunk,createSlice } from "@reduxjs/toolkit"
import axios from "axios"
import apiHandler from "../../api/apiHandler"
const initialState = {
breakingNews:[],
general:[],
technology:[],
sports:[],
business:[],
status:'idle',
error:null
}
export const fetchBreakingNewsData = createAsyncThunk('news/breakingNews',apiHandler.getBreakingNewsData)
export const fetchGeneralNewsData = createAsyncThunk('news/generalNews',apiHandler.getCategoryNewsData('general'))
export const fetchTechnologyNewsData = createAsyncThunk('news/technologyNews',apiHandler.getCategoryNewsData('technology'))
export const fetchSportsNewsData = createAsyncThunk('news/sportsNews',apiHandler.getCategoryNewsData('sports'))
export const fetchBusinessNewsData = createAsyncThunk('news/businessNews',apiHandler.getCategoryNewsData('business'))
const newsSlice = createSlice({
name:'news',
initialState,
extraReducers(builder){
builder
.addCase(fetchBreakingNewsData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchBreakingNewsData.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.breakingNews = state.breakingNews.concat(action.payload)
})
.addCase(fetchBreakingNewsData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(fetchGeneralNewsData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchGeneralNewsData.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.general = state.general.concat(action.payload)
})
.addCase(fetchGeneralNewsData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(fetchTechnologyNewsData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchTechnologyNewsData.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.technology = state.technology.concat(action.payload)
})
.addCase(fetchTechnologyNewsData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(fetchSportsNewsData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchSportsNewsData.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.sports = state.sports.concat(action.payload)
})
.addCase(fetchSportsNewsData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(fetchBreakingNewsData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchBusinessNewsData.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.business = state.business.concat(action.payload)
})
.addCase(fetchBusinessNewsData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
export default newsSlice.reducer
Please help me with the following error.
And if multiple api calls add cases is not possible in createSlice and how can we do it with redux toolkit.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Эта ошибка возникает, когда я подключаю действия к extraReducers. Мой код
export const fetchCountries = createAsyncThunk(
`country`,
async (organizationId: string) => {
export const saveCountry = createAsyncThunk(
`country`,
async ({ } => {})
const regions = createSlice({
name,
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(fetchCountries.pending, isFetching);
builder.addCase(fetchCountries.rejected, error);
builder.addCase(fetchCountries.fulfilled, (state, action) => {});
builder.addCase(saveCountry.pending, isFetching);
builder.addCase(saveCountry.rejected, error);
builder.addCase(saveCountry.fulfilled, (state, {payload}) => {});
И если я запускаю, я получаю эту ошибку: Error: addCase cannot be called with two reducers for the same action type
4 ответа
В моем случае было показано то же сообщение об ошибке, но это была другая ошибка:
.addCase(setAddress.pending, (state, action) => {
state.setAddressStatus = 'Pending';
})
.addCase(setAddress.fulfilled, (state, action) => {
state.setAddressStatus = 'Fulfilled';
})
.addCase(setAddress.fulfilled, (state, action) => { // I repeated fulfilled
state.getAddressesStatus = 'Rejected';
console.error(action.error);
})
Мне потребовалось несколько минут, чтобы найти проблему, может помочь кому-то.
3
Bruno Silva
21 Май 2022 в 20:42
В CreateAsycThunk вы упомянули обе строки с одинаковым именем, она должна быть такой
export const fetchCountries = createAsyncThunk(
`country`,
async (organizationId: string) => {
export const saveCountry = createAsyncThunk(
`saveCountry`,
async ({ } => {})
const regions = createSlice({
name,
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(fetchCountries.pending, isFetching);
builder.addCase(fetchCountries.rejected, error);
builder.addCase(fetchCountries.fulfilled, (state, action) => {});
builder.addCase(saveCountry.pending, isFetching);
builder.addCase(saveCountry.rejected, error);
builder.addCase(saveCountry.fulfilled, (state, {payload}) => {});
1
Anuj
29 Апр 2022 в 07:17
Createasyncthunk имеет два основных параметра: один типа string и вторая функция обратного вызова, которая имеет API и thunk в качестве параметров. Вы, вероятно, простите, если в вашем фрагменте есть один асинхронный преобразователь, который имеет «» в качестве данных, но если у вас есть два или более асинхронных преобразователя, то проверки будут выполняться для каждого преобразователя, если два или более из них имеют похожие «» или «идентичные» имена, то вы получите тревожную ошибку. «createAsyncThunk: ошибка: addCase нельзя вызвать с двумя редукторами для одного и того же типа действия»
0
Proau
20 Фев 2022 в 17:10
Это происходит потому, что в моих действиях мало действий AsyncThunks с одинаковым typePrefix.
Поэтому у него должны быть разные имена:
export const fetchCountries = createAsyncThunk(
`getCountry`, //<------ this first argument (name) must be unique
async (organizationId: string) => {
export const saveCountry = createAsyncThunk(
`postCountry`,
async ({ } => {})
39
Alexey Nikonov
1 Май 2022 в 22:18