Search code examples
javascriptreactjsredux-toolkit

Should I call other reducer functions inside my redux reducers?


I have a complex state where based on certain conditions other reducer functions are called. Is it bad practice to call reducers in other reducers?

I'm making a custom hook that wraps the dispatch calls, because I might want some other logic (backend calls, logging etc) to go along with the dispatch.

I'm torn between doing what I have now or I could take the conditionals out of the reducers, and in the custom hook have conditionals in there.

From my research theres a few things I can do.

  1. Leave it how it is
  2. Extract the logic into thunks, and simplify the reducers
  3. Possibly use Selectors to calculate the derived data, but that might not update my state how I want it to
export const game = createSlice({
  name: 'game',
  initialState,
  reducers: {
    incrementScale: (state) => {
      state.currentScale = state.scales.pop()
      if (!state.currentScale) {
        state.isGameInProgress = false
      } else {
        game.caseReducers.incrementNote(state)
      }
    },
    incrementNote: (state) => {
      state.currentNote = state.notes.shift()
      if (!state.currentNote) {
        game.caseReducers.incrementScale(state)
      }

      state.triesLeft = INTIAL_TRIES
    }
  }
})

The custom hook

export function useGame() {
   const handleIncrementNote() {
     //put the undefined checks and other logic in here instead?
     dispatch(incrementNote)
   }

}

Solution

  • I'm inclined to say no, you shouldn't call reducers from other reducers, but in this case I think it may "safe" to do so since you aren't going outside the slice, though I do see a potential edge-case when both scales and notes are empty arrays where each case reducer calls the other and creates a loop and never returns.

    My suggestion would be to just apply the logic in each reducer case directly and independent of any other reducer to remove the chance of the logic looping between them:

    export const game = createSlice({
      name: 'game',
      initialState,
      reducers: {
        incrementScale: (state) => {
          state.currentScale = state.scales.pop();
    
          if (!state.currentScale) {
            state.isGameInProgress = false;
          } else {
            // game.caseReducers.incrementNote(state)
            state.currentNote = state.notes.shift();
            state.triesLeft = INTIAL_TRIES;
          }
        },
        incrementNote: (state) => {
          state.currentNote = state.notes.shift();
    
          if (!state.currentNote) {
            // game.caseReducers.incrementScale(state)
            state.currentScale = state.scales.pop();
          }
    
          state.triesLeft = INTIAL_TRIES;
        },
      },
    });