M. Mirra's notebook[?]

created: 2019/05/06, tags: redux

redux modal reducers: the 80/20 of state machines without libraries

from "the rise of the state machines":

We are thinking linearly and basically trying to cover all possible directions to the final result. One step leads to another, and quickly we would start branching our code. What about problems like the user double-clicking the button, or the user clicking the button while we are waiting for back end's response, or the request succeeding but the data being corrupted. In these cases, we would probably have various flags that show us what happened. Having flags means more if clauses and, in more complex apps, more conflicts.

all redux programs are state machines but they rarely are explicit state machines. the power of the model lies not in the machine-level mechanics but in the ability it provides to create a system-description language that is both human-friendly and precise enough for the machine. for redux in particular, it can bridges the low-level, fine-grained complexity of the state tree with the high-level, coarse-grained simplicity of thought.

problem 1: the word "state" is taken already

the word "state" in redux means the entirety of the state tree. the same word in the context of "finite state machines" means a specific configuration of the state tree and the operations that are allowed in that configuration. a different word is needed to avoid confusion. I suggest mode.

"modal" dialogs iscards all clicks and key presses except those related to the "ok" and "cancel" actions. "modal" editors (such as vi) discard all text input while in "command mode".

problem 2: the "redux way" is action-centric

redux programs are written in an action-centric way:

  const reducer = (state, action) => {
    switch (action.type) {
      case 'ACTION_1':
        // ...
        return { ...state, /* ... */ }

      case 'ACTION_2':
        // ...
        return { ...state, /* ... */ }

      default:
        return state
    }
  }

programs based on state machines are written in a state-centric way. from the article quoted:

  const machine = {
    'idle': {
      click: function () { ... }
    },
    'fetching': {
      success: function () { ... },
      failure: function () { ... }
    },
    'error': {
      'retry': function () { ... }
    }
  }

a low-impact tactic to bring benefits of state machines to redux is to add a top-level "mode" property to the state tree that indicates the named state.

  const initialState = {
    mode: 'init',
    // ...
  }

then, instead of white-listing actions in state definitions, assert states in action handlers:

  const reducer = (state, action) => {
    switch (action.type) {
      case 'FETCH_FAILURE':
        return {
          ...state,
          mode: 'error'
        }
        
      case 'RETRY':
        if (state.mode !== 'error') {
          return state
        }
        // ...
        return { ...state, /* ... */ }
    }
  }

[TODO add note about reducer wrapper exception-based state assertions]