21

I want to know if it's possible (or a good practice) to call dispatch(someDumbAction()) from an extraReducer.

For example, I have a setData() action in reducers object from createSlice. I want to call setData() directly in my component. But I want to call it too in a extraReducer listener, in order to reuse the reducer logic, like below:

// Thunk Action
export const getData = createAsyncThunk('data/getData', async (params) => {
  return await api.get({ params })
})

// Slice creation
const slice = createSlice({
  name: 'data',
  initialState: [],
  reducers: {
    setData: (state, { payload }) => {
       state.push(payload);
    })
  },
  extraReducers: (builder: any) => {
    builder.addCase(getData.pending, (state) => {
      //...
    })
    builder.addCase(getData.rejected, (state) => {
      //...
    })
    builder.addCase(getData.fulfilled, (state, { payload }) => {
      // Here I want to dispatch `setData` action, in order to reuse that logic
      // dispatch(setData(payload));
      
    })
  },
})

// In any component: dispatch(setData([...]);
sevenlops
  • 456
  • 1
  • 4
  • 18

1 Answers1

22

No. Reducers can never dispatch actions:

https://redux.js.org/style-guide/style-guide#reducers-must-not-have-side-effects

However, it looks like what you're really asking for here is the ability to run the same state update logic steps in multiple situations.

You could define the logic as a standalone reducer function, and reuse it in both cases:

function addItem(state, action) {
  state.push(action.payload);
}

const slice = createSlice({
  name: 'data',
  initialState: [],
  reducers: {
    setData: addItem
  },
  extraReducers: (builder: any) => {
    builder.addCase(getData.pending, (state) => {
      //...
    })
    builder.addCase(getData.rejected, (state) => {
      //...
    })
    builder.addCase(getData.fulfilled, addItem)
  },
})

You could also define the function as part of reducers, and then reference it inside of the extraReducers handler:

const slice = createSlice({
  name: 'data',
  initialState: [],
  reducers: {
    setData: (state, { payload }) => {
       state.push(payload);
    })
  },
  extraReducers: (builder: any) => {
    builder.addCase(getData.pending, (state) => {
      //...
    })
    builder.addCase(getData.rejected, (state) => {
      //...
    })
    builder.addCase(getData.fulfilled, (state, action) => {
      slice.caseReducers.setData(state, action);
    })
  },
})
markerikson
  • 63,178
  • 10
  • 141
  • 157
  • 1
    Yes, only want to avoid repeating the same line of code in the same file. The first solution has sense, but the second one it's a bit strange. Is even possible to reference the same object you're creating (slice) inside itself? :thinking: – sevenlops Dec 02 '20 at 17:01
  • 3
    Yep! That line of code inside this reducer won't run until after the slice object has been defined. That's why I _didn't_ do `builder.addCase(getData.fulfilled, slice.caseReducers.setData)` - because that line would run _as_ the slice is being created and thus likely not work right. – markerikson Dec 02 '20 at 17:20
  • Oh thanks man! It has sense now ^^ Only for curiosity, do you recommend the use of `extraReducers` and listeners like pending, rejected.. or you'd go with old known `dispatch(someActionSuccess())` and `dispatch(someActionError())` ? Because, in RTK docs, seems that are recommending the last approach. Thank again Mark. – sevenlops Dec 02 '20 at 17:49
  • 1
    The RTK tutorials were written before we added the `createAsyncThunk` API. `createAsyncThunk` does exactly what you're describing, it just auto-generates the action creators for you. The new ["Redux Essentials"](https://redux.js.org/tutorials/essentials/part-1-overview-concepts) and ["Redux Fundamentals"](https://redux.js.org/tutorials/fundamentals/part-1-overview) tutorials in the core docs are up to date - please refer to those. Also, FYI, we just released a new ["RTK Query" lib](https://rtk-query-docs.netlify.app) as an alpha that simplifies data fetching - check it out! – markerikson Dec 02 '20 at 18:13
  • You are the best! RTK Query looks promising. By last, one more doubt, now about `useSelector` hook and `createSelector` function. When you create a selector with `createSelector`, it needs to be called with `useSelector` hook or it could be called directly as normal function?. Docs not explain this (are using `mapStateToProps` yet?!). Thanks again, Mark. – sevenlops Dec 03 '20 at 08:40
  • 1
    Selectors are standalone JS functions. For Redux, they normally take the `state` as the first arg, and return some extracted / derived value. "Memoized" selectors are normally created with the Reselect lib. See [Using Reselect Selectors for Encapsulation and Performance](https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-using-reselect-selectors/). Selectors can be used anywhere you have access to the whole Redux state, including `useSelector`, `mapState`, and inside of thunks and sagas: `const value = selector(state)` – markerikson Dec 03 '20 at 15:30
  • 2
    @markerikson, if I want to call one async thunk when another one gets fulfilled, how should I do that ? – asif.ibtihaj Jul 05 '21 at 08:48
  • @asif.ibtihaj may I know the answer to your question? – agassaa Aug 28 '22 at 02:01