Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux reducer / state-shape design for dependent state-slices

I enjoy the concept of reducer composition in Redux, but have run into a scenario where I would like to split a reducer up, but the children reducers would then rely on state-slices from the others to make their changes.

For Example

In my state, I need to keep track of the following:

  • A range of possible ranks (i.e. [ 2, 3, 4, 5, 6 ])
  • The currently selected rank (one of the above values).
  • Depending on the selected rank, a range of possible training levels. The relationship is that the range goes from [ 1 .. (selectedRank - 1) ]
  • A currently selected training level within the range above

Initially, I had a larger reducer which encapsulated all of these aspects:

function rankAndTraining(state = {
  selectedRank            : 4,
  availableRanks          : [ 2, 3, 4, 5, 6 ],
  availableTrainingLevels : [ 1, 2, 3 ],
  selectedTrainingLevel   : 2,
}, action) {
  .
  .
  .
    case SELECT_RANK: 

      let newRank = action.rank;

      if(!availableRanks.contains(newRank)) {
        // If new rank is not an allowed value, do nothing.
        return state;
      }

      // Update selectedRank with newRank...

      // Update availableTrainingLevels array based on newRank...
      // [ 1 ... (selectedRank - 1) ]

      // Update selectedTrainingLevel if it is now out of range 
      // of availableTrainingLevel (i.e. set to highest value of new range)

      return state;
  .
  .
  .
}

I wanted to split this reducer up, as I felt that the ranks and the training levels could be maintained in separate reducers.

However, there are dependencies across the state pieces that would still need to be handled if the reducer was split, such as:

  • If the new rank is invalid (ex. it is not in the availableRanks), then the training level bits should not be updated.

If I were to split the training level bits into another reducer, then they would have no way to know the result of this "rank check", since all they would "see" in their state-slice is the training level pieces.

In this scenario, is the above reducer really the "smallest slice" of state that I can get, given the dependencies between them? Or is there a better way to split it that I may not be seeing?

like image 673
lawls544 Avatar asked Oct 29 '15 07:10

lawls544


1 Answers

If you use the Redux thunk middleware you can inspect the entire state first before you actually dispatch your action at all instead of dispatching your action and then conditionally update your state in the reducer:

function selectRankIfAllowed() {
  return (dispatch, getState) => {
    const { availableRanks } = getState(); 

    if(!availableRanks.contains(newRank)) {
      // If new rank is not an allowed value, do nothing.
      return state;
    }

    dispatch(selectRank());
  };
}

https://github.com/gaearon/redux-thunk

like image 91
Markus-ipse Avatar answered Nov 14 '22 01:11

Markus-ipse