Search code examples
javascriptreactjsreduxmiddleware

Redux: turn middleware on and off


I am looking for a way to turn a middleware on and off. I introduced a tutorial functionality - I listen to what the user is doing with the UI by checking each action with a "guidance" middleware. if the user clicks on the right place he moves to the next step in the tutorial. However this behaviour is only needed when the tutorial mode is on. Any ideas?

const store = createStore(holoApp, compose(applyMiddleware(timestamp, ReduxThunk, autosave, guidance),
window.devToolsExtension ? window.devToolsExtension() : f => f)); 

for now my solution was to keep the "on" switch in a guidanceState reducer and dirty check it in the middleware:

const guidance = store => next => action => {

    let result = next(action)

    const state = store.getState();
    const { guidanceState } = state;
    const { on } = guidanceState;

    if (on) {

 ....

However, ~95% of the time the tutorial mode would be off so dirty checking every action all the time feels a bit, well, dirty... ;) Any other ways?


Solution

  • Don't do stateful things in middleware (unless you have a good pattern for managing that state, like Sagas). Don't do stateful things with your middleware stack at all if you can avoid it. (If you must do so, @TimoSta's solution is the correct one).

    Instead, manage your tours with a reducer:

    const finalReducer = combineReducers({
      // Your other reducers
      tourState: tourReducer
    });
    
    function tourReducer(state = initalTourState, action) {
      switch(action.type) {
        case TOUR_LAST_STEP:
          return /* compose next tour step state here */;
        case TOUR_NEXT_STEP:
          return /* compose last tour step state here */;
        case TOUR_CLOSE:
          return undefined; // Nothing to do in this case
        default:
          return state;
      }
    }
    

    Then, in your application use the current state of tourState to move the highlighting, and if there is nothing in tourState, turn the tour off.

    store.subscribe(() => {
      const state = store.getState();
      if (state.tourState) {
        tourManager.setTourStep(state.tourState);
      } else {
        tourManager.close();
      }
    });
    

    You don't have to use a stateful tour manager either - if you're using React it could just be a component that pulls out tourState with a connect wrapper and renders null if there is no state:

    // waves hands vigorously
    const TourComponent = (props) => {
      if (props.currentStep) return <TourStep ...props.currentStep />;
      return null;
    }