Uncaught error addcase cannot be called with two reducers for the same action type

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 ...

@lucassus

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?

ericrav, kirkobyte, dwilt, kalyan-csiro, hibearpanda, martin8877, atulic, sesifer, Jay-zsy, elpatronaco, and 20 more reacted with thumbs up emoji

@phryneas

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.

@markerikson

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.

@kirkobyte

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.

@phryneas

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);
  }
}
markerikson, msutkowski, melanieseltzer, lucassus, Jimmerz28, rijulg, coddingtonbear, dwilt, SeanMcP, Aeet, and 14 more reacted with thumbs up emoji
maxmarinich reacted with confused emoji
xmd5a, k-sav, oldo, brandonvilla21, and Odme reacted with rocket emoji

@SaeidEsk

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

@phryneas

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.

xmd5a, Kavtorev, lucassus, TmVukov, ranka23, ludicasoft, elpatronaco, xralphack, EvHaus, rhoiyds, and 48 more reacted with thumbs up emoji
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

@xmd5a

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.

I was looking today for a solution of this problem and this answer is pretty neat — thanks for sharing! 🔥

@elpatronaco

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.

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

@borisilic-ap

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.

@markerikson

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

Понравилась статья? Поделить с друзьями:
  • Uncaught error actions must be plain objects use custom middleware for async actions
  • Uncaught error a url property or function must be specified
  • Uncar dll вернул код ошибки 11
  • Unc ошибка mhdd
  • Unbuckling rear belt ошибка