0

I've been working on a react application lately and I'm using a big usereducer function to hold the global state.

I've been trying to use a sort of state machine pattern where I case on a string constant which is a name for the current state. Here's an example:

// First declaring an object which represents the possible states the app can be in:

const possibleStates = {
  IDLE:"IDLE",
  ITEM_SELECTED:"ITEM_SELECTED",
}

// Here's what the reducer function might look like:

function stateReducer(state,action){

 switch (state.stateName){
   case possibleStates.IDLE:
   // Logic
  Return newState;
  
  case possibleStates.ITEM_SELECTED: 
  // Logic
  Return newState;

}

This is all well and good, but then in each state case I need to switch on the action type to decide what to do based on the action:

function stateReducer(state, action) {

  switch (state.stateName) {
    case possibleStates.IDLE:

      switch (action.type) {
        case "ITEM_CLICKED":
          //logic . . .
          return newState;

        default:
          return state;
      }

    case possibleStates.ITEM_SELECTED:

      switch (action.type) {
        case "DELETE_ITEM":
          //logic . . .
          return newState;

        default:
          return state;
      }
    default:
      return state;
  }
}

Then many times I find myself needing ANOTHER switch statement inside the action switch statement, Eg if we've selected an item we need different logic for the type of item we've selected. This puts us 3 layers deep in switch statements.

Now, I enjoy having all the logic of the app in this one big function. It means I can pretty easily find where bugs are by logging the states then control-F ing my way to the previous state name before the bug happened. Then I know I'm looking through the logic that caused the bug. It also feels nice that there's a big block of logic clearly separated from the React components which I can make pretty dumb. This means they can purely represent the view of the app and the reducer can purely represent the logic.

But there are obvious downsides to these deeply nested switch statements though: what if I forget a break or return? Then I've wrecked the whole reducer, and it can be confusing to tell how deep I am and where to add a new action or possible state. Also my reducer is starting to get pretty long - like 500-600 lines long.

I could write a different function for each possible state, then case on the state then throw it to another function:

  function stateReducer(state, action) {

  switch (state.stateName) {
    case possibleStates.IDLE:
      return idleReducer(state, action);

    case possibleStates.ITEM_SELECTED:
      return selectedReducer(state, action);

    default:
      return state;
  }
}

function idleReducer(state, action) {
  //logic . . .
  return newState;
}

function selectedReducer(state, action) {
  //logic . . .
  return newState;
}

Then after that I could go another layer and rip out functions into each action.

Do you think I should bite the bullet and pull my reducer logic out into functions or should I continue with my 500 line reducer and my nested switch statements. Is there a better pattern for this?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • for years i would have said break this down into functional units - as this allows the code to be called else where - but after many conversations, and understand TDD - i've learnt to stop. as you say the code is easier to read in one place, the decomposition is functionally equivalent - and the process of decomposition could introduce errors. but more importantly it takes time - and who is paying for this time? ref ```https://en.wikipedia.org/wiki/Test-driven_development``` – developer Jun 29 '21 at 11:06
  • cont.. what are the current function benifits to the application for this change - what are the costs in time of this change.. - your as a member of your team are a "resource", and if there are no requirements for this decomposition you need to be "agile" and move on to your next task. as you said - this is easier to read in one place - thus easier to maintain. the agile way would be to refactor often as required - if it is not required don't do it, and only do it when it is – developer Jun 29 '21 at 11:11

0 Answers0