30

I have this small module management slice:

const WorkspaceSlice = createSlice({
    name: "workspace",
    initialState: {
        activeModule: null,
        modules:
        {
            ["someModule"]: { ...moduleRenderingData },
        },
    },
    reducers: {
        setActiveModule(state, action)
        {
            state.activeModule = action.payload;
        },
        addModule(state, action)
        {
            const { moduleName, renderingData } = action.payload; 
            if (!state.modules[moduleName])
            {
                state.modules[moduleName] = renderingData;
            }

            // state.activeModule = moduleName; <- basically the same as 'setActiveModule'
            this.setActiveModule(state, action.payload.module); // <- 'this' is undefined
        },
    },
});

export default WorkspaceSlice;

What I'm trying to do is call setActiveModule from within addModule so that I won't have duplicate code, but I get an error because this is undefined.
Is there a way to call one reducer from within another? What's the syntax for that?
If not, is it possible to achieve this functionality in another way, assuming I want to keep both addModule and setActiveModule as separate actions?

Asaf Sitner
  • 599
  • 1
  • 10
  • 18

2 Answers2

48

The slice object returned from createSlice includes each of the individual case reducer functions you passed in as slice.caseReducers, to help with situations like this.

So, you could do:

addModule(state, action) {
    const { moduleName, renderingData } = action.payload; 
    if (!state.modules[moduleName]) {
        state.modules[moduleName] = renderingData;
    }

    WorkspacesSlice.caseReducers.setActiveModule(state, action);
},

Also, reducer functions have no this, because there are no class instances involved. They're just functions.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Thank you very much! I didn't know about caseReducers - I tried with .reducers, but as expected that didn't work either. – Asaf Sitner Aug 24 '20 at 16:14
  • what if have used this passed my reducers to slice.reducers? – Jesswin Nov 07 '21 at 18:45
  • @Jesswin : not sure what you're asking there - can you clarify? – markerikson Nov 08 '21 at 16:44
  • Calling one reducer from within another, ends in a typescript error `slice implicitly has any type, since it is being referenced in its own initializer`. Although it might work if you want to use the case reducer outside the component, I'm not sure it works from within another reducer. – sayandcode Dec 08 '22 at 12:12
  • @sayandcode try defining the reducer outside of the slice and then adding it to the `reducers` object, as shown here: https://stackoverflow.com/questions/63564530/is-it-possible-to-call-a-reducer-function-from-another-reducer-function-within This fixed typing issues for me. – Hadas Arik Dec 26 '22 at 13:25
  • @HadasArik That gets rid of type inference for the `state` and `action` right? – sayandcode Dec 28 '22 at 10:44
  • Yes, which is why we show defining those case reducer functions _inside_ the `createSlice` call as the default approach. You do normally need to declare `action: PayloadAction`, but you get `state` inferred automatically. – markerikson Dec 28 '22 at 20:34
2

The Redux Toolkit Docs have an example,You can read how to do it:

const slice = createSlice({
  name: 'test',
  initialState: 0,
  reducers: {
    increment: (state, action: PayloadAction<number>) => state + action.payload,
  },
})

// also available:
slice.caseReducers.increment(0, { type: 'increment', payload: 5 })
謝育能
  • 33
  • 1
  • 5