As you may already be aware, a high order function is a function that operates on another function and returns a new function. This is a type of functional composition.

If you’ve used Redux for sometime you will also be aware that Reducers are simply functions which accept state and an action and return a new state value.

As a result, of this it is possible – and often highly useful – to apply functional composition to our reducers in order to enhance them with additional functionality. We call these Higher Order Reducers.

Example of a Higher Order Reducer

Let’s say with have a reducer which manages the toggle state of sections of a sidebar menu. Each sidebar section can be either open or closed.

const expansionReducer = (
  state = {
    isOpen: false,
  },
  action
) => {
  switch (action.type) {
    case SECTION_TOGGLE:
      return {
        isOpen: !state.isOpen,
      };
    case SECTION_EXPAND:
      return {
        isOpen: true,
      };
    case SECTION_COLLAPSE:
      return {
        isOpen: false,
      };
    default:
      return state;
  }
};

To ensure the state is manageable we need to apply our expansionReducer to each “section” in the siderbar menu. To do this we can use a reducer composition utility such as keyedReducer (for an example of such a utility see the WP-Calypso state utils) to “key” our state by sidebar section:

export default keyedReducer("sidebarSection", expansionReducer);

The result is a state shape that looks similar to this:

{
    'section-1': {
        isOpen: false,
    },
    'section-2': {
        isOpen: false,
    },
    'section-3': {
        isOpen: false,
    }
}

To expand any given section all we need now do is dispatch an action with the following shape and the corresponding section (only) will have its isOpen flag set to true in state:

{
    type: SECTION_EXPAND,
    sidebarSection: 'section-2'
}

However, consider what might happen should we need to expand/collapse all the menu sections in one go?

How might we go about doing that?

Acting on all sidebar sections

One thought might be to create a new action type (eg: COLLAPSE_ALL_SECTIONS) and handle that in the expansionReducer.

Recall however, that the expansion reducer is called on a per section basis (eg: section-2, section-3…etc) and so any actions handle there woudl only effect their slice of state (eg: only section-2 and not section-3…etc). Therefore, to collapse all sections would require dispatching multiple actions – one for each section. Hardly ideal.

The problem is that whilst the keyedReducer saves us time (and cognative load) by automatically creating a reducer that is keyed by section, it does not allow us access to handle actions that might need to occur on all sections.

Making use of higher order reducers

One way to handle such a problem is by making use of a higher order reducer to handle our “global” sidebar menu actions (those which apply to all menu items).

Such a reducer will need to accept the original reducer and return a new “composed” reducer function which still calls the original reducer but also allows for implementing its own reducer logic to handle additional actions.

One implementation of this could look akin to the below:

const withAllSectionsSidebarControls = (reducer) => (state, action) => {
  switch (action.type) {
    case COLLAPSE_ALL_SECTIONS:
      return Object.keys(state).reduce((acc, curr) => {
        acc[curr] = {
          ...state[curr],
          isOpen: false,
        };

        return acc;
      }, {});
    default:
      // Call original section-specific keyed reducer
      return reducer(state, action);
  }
};

To make use of this we simply wrap the higher order reducer around the original reducer, passing the original reducer as an argument:

withAllSectionsSidebarControls(
  keyedReducer("sidebarSection", expansionReducer)
);

Note that the higher order reducer is making use of the inline arrow function “LAMDA” syntax so that it returns a new reducer function.

It is this resulting reducer function that will be used as the final reducer for this slice of state.

In doing this it consumes the original “keyed” reducer function, ensuring it is called in the default case, but allowing other “global” actions to be handled by the reducer logic for the COLLAPSE_ALL_SECTIONS action type.

As a result, we can now dispatch an object with the following shape and all sidebar sections will be collapsed:

{
    type: COLLAPSE_ALL_SECTIONS,
}

Conclusion

Hopefully you can see where this higher order pattern might come in handy for your application?

Whilst reading the above, you may have also spotted that the implementation of keyedReducer is itself actually a higher order reducer. So in fact we’re now composing multiple reducer functions together!

We could of course use a compose utility for that, but that’s something to leave for another day.

Let me know if you found this useful.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.