Maybe Better Reducers

- 5 mins

Redux is one of them most elegant ideas in the Javascript ecosystem in the last few years. Inspired by Facebook’s Flux architecture, this small but brave library helps you to build applications based primarily on immutable data structures and pure functions. Folks with bias on Functional Programming (like me) will feel at home with Redux.

In summary, a Redux app is a container for a immutable data structure representing the current state. That container accepts actions which are just plain objects describing user intent. Once an action is accepted, a reducer function runs and creates the next state based on the action and the current state. The reducer function then is the core of a Redux app.

The Reducer

Redux doesn’t impose any restrictions on the way you define your reducer, as long as it comforms with the following signature:

reducer :: (State, Action) -> State

Most examples all over the internet - including the one in the official documentation - will suggest you to define the reducer function using a switch statement on the action type.

function reducer (state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return state.concat({
        text: action.text,
        completed: false
      });
    case "TOGGLE_TODO":
      return state.map((todo, index) =>
        index === action.index ?
          Object.assign({}, todo, { completed: !todo.completed }): todo
      );
    default:
      return state;
  }
} 

This is literally the reducer suggested in the Redux official docs. I wonder if people are really following this approach (I hope not). As you can see, the code above is very busy to read and not at all modular. Hopefully, we can do better than that. Let’s see.

Introducing Maybe

The algorithm of the reducer we just saw can be described as:

  • It chooses a behavior by pattern-matching the action.type;
  • Then, it executes the matching behavior returning a new state;
  • In case there is no match, it returns the unchanged state.

Turns out that logic can be accuratelly implemented using a Maybe container, as the Haskell programmers would probably suggest here. Java 8 developers can think of it as an Optional. In Javascript, we can get Maybe from the amazing Folktale lib. Let’s see the result.

import Maybe from "data.maybe";

const reducer = (state, action) => 
  Maybe
    .fromNullable(actionHandlers[action.type])
    .map(handler => handler(state, action))
    .getOrElse(state);

The Maybe version of our reducer relies on an addional step: we extracted all the action-specific behaviors to separate functions, which are accessible in a map we called actionHandlers.

const ADD_TODO = (state, action) =>
  state.concat({ text: action.text, completed: false });

const TOGGLE_TODO = (state, action) =>
  state.map((todo, index) =>
    index === action.index ?
      Object.assign({}, todo, { completed: !todo.completed }): todo);

const actionHandlers = { ADD_TODO, TOGGLE_TODO };

Now each action handler is an independent function with a single and succint responsibility. The same applies to the reducer which uses the Maybe container to drive the logic around the pattern matching. And just that.

Those who are not familiar with Maybe can think of it as a container for a value that might or might not be present. In our example, an action type might or might not be found in the map. The .fromNullable constructor does that for us: it gets an actual function or it gets nothing in case there is no match. Next, we call .map which executes a present behavior. The interesting thing about .map is that is does not run in case the Maybe is holding an empty value. The last call .getOrElse returns the value from inside the container if the value is present. Otherwise, it uses the default argument, which in our case is the unchanged state.

Conclusion

Redux is great not only because it is so simple and concise but also because it is based on strong Functional Programming concepts. Additionally, Maybes, Eithers, Validations and other containers from Folktale are very handy in a diversity of situations, but specially useful when it comes to functional error handling. The reducer example is just a small demonstration on how well the Redux idea fits with other FP based libs. For a complete and more detailed example take a look at my Redux + Folktale + Ramda Todo App.

Update

The idea behind this post inspired the development of the Redux Definer NPM package. Pretty handy to avoid reducer boilerplate code. In case you also want to avoid action creators boilerplate, you can go straight to Redux Actions.

Vinicius Gomes

Vinicius Gomes

Software Developer

comments powered by Disqus
Vinicius Gomes © 2018
rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium