Introducing Redux Preboiled

Both at Futurice and in my spare time, I’m working a lot on apps based on Redux. More often than not, the Redux parts tend to accumulate a good amount of boilerplate code - action type constants, action creator declarations, reducer switch statements with the same default: return state clauses, and so on.

Of course, it’s possible to reduce some of this noise by writing utility functions, and sometimes I do so. However, taking this too far is dangerous: when getting into a Redux codebase, few things are worse than discovering a proprietary, badly documented framework on top of Redux that you need to understand and learn to use, only to forget about it once you move to the next project.

As a possible way out of this dilemma, I often looked into using an existing library of Redux helpers instead of rolling my own. This would make the helpers more valuable to learn as they can be reused across projects, and the documentation would also likely be much better. However, I could never really find a library that looked quite like what I wanted.

Basically, what I’ve been looking for is a “Lodash for Redux” of sorts - a cohesive collection of simple, stand-alone helper functions that I can pick and choose from based on my project’s needs, without having to commit to an all-in-one framework like Rematch (which is nice for some cases, but not such a good fit for others). Also, I need something that works great with TypeScript as my project work increasingly relies on it.

No library I found checked these boxes properly, so I started creating my own. Several weeks later, I am happy to announce that I have now released the result of this effort as Redux Preboiled.

Redux helpers, served a-la carté

Redux Preboiled is a library of boilerplate-reducing Redux helper functions, designed to fit together nicely while still being usable individually. It is written in and optimized for TypeScript, while still being a good fit for pure-JavaScript apps. A special focus for me has been simplicity - instead of relying on clever metaprogramming tricks that might have shaved off a few more lines here and there, I wanted small and “dumb” functions that are easy to understand in behavior and implementation, and which can be easily composed to do greater things.

The initial set of helpers is still very small. However, it should already help with two of the most common sources of Redux boilerplate: defining actions and writing basic reducers.

Let’s look an example - in fact, let’s look at the example of all examples in the Redux world, good old counter. For extra excitement, we’re going to throw in a parameterized multiply action to complement increment.

Here is how you might write such a counter in vanilla Redux, assuming you follow the patterns recommended in the official Redux documentation:

// Action Types

const INCREMENT = 'increment'
const MULTIPLY = 'multiply'

// Action Creators

const increment = () => ({
  type: INCREMENT
})

const multiply = amount => ({
  type: MULTIPLY,
  payload: amount
})

// Reducer

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT:
      return state + 1
    case MULTIPLY:
      return state * action.payload
    default:
      return state
  }
}

This is far from terrible, but there is already some fluff here. Note how for each type of action we define, we need to write two declarations - and action type constant and a corresponding action creator function. This approach has benefits, of course, but it’s still cumbersome. Also, for every other reducer like counterReducer, we’ll need to repeat the switch (action.type) { … } ceremony and make sure not to forget the default: return state clause at the end.

Here is how you could boil down things with Redux Preboiled:

import {
  chainReducers,
  createAction,
  onAction,
  withInitialState
} from 'redux-preboiled'

// Actions

const increment = createAction('increment')
const multiply = createAction('multiply').withPayload()

// Reducer

const counterReducer = chainReducers(
  withInitialState(0),
  onAction(increment, state => state + 1),
  onAction(multiply, (state, action) => state * action.payload)
)

We have reduced the number of lines from 28 to 19, six of which are the import statement. This means that, ignoring imports (which only matter so much here because the example is so small), Preboiled helped us cut the length of the action and reducer definitions in half, thanks to four helpers:

  • createAction, which generates an action creator given an action type (optionally with a payload parameter) and makes the action type available as a type property on that action creator (e.g., increment.type), making a separate action type constant unnecessary;

  • onAction and withInitialState, which let you create sub-reducers that handle specific action types or provide an initial state, respectively;

  • and chainReducers, which pulls our sub-reducers together by arranging them to a pipeline (or “call chain”).

As can be seen above, these helpers are designed to fit well with each other. For example, onAction allowed us to specify the action type by passing one of the action creators we generated with createAction (e.g., increment), instead of having to explicitly pass the corresponding action type (increment.type or 'increment'). The latter option is also available - and makes onAction usable stand-alone - but the former offers additional convenience if you use both helpers together.

Goes well with TypeScript

One thing which sets Redux Preboiled apart from many similar libraries is its focus on a TypeScript-friendly design. This doesn’t only mean making the helpers easy to type statically, but also exploiting opportunities for automatic type inference wherever possible.

As an example, let’s look at onAction again. As mentioned before, it accepts an createAction action creator in place of of an action type. I added this option not only for convenience, but also to offer better type system assistance: because the action creator’s static type includes the shape of the actions it produces, TypeScript’s compiler can infer the type of action argument passed to the state update function without me having to specify it. It will also give me an error if I explicitly specify a type that is not compatible with the inferred one.

// TypeScript

import { createAction, onAction } from 'redux-preboiled'

const multiply = createAction('multiply').withPayload<number>()

const onMultiply1 = onAction(multiply, (state: number, action) => {
  // `action` is inferred to be of type
  // PayloadAction<number, 'multiply'>.
  return state + action.payload
})

const onMultiply1 = onAction(
  multiply,
    (state, action: PayloadAction<string>) => {
    // ERROR: Argument of type 'PayloadActionCreator<number, "multiply">'
    // is not assignable to parameter of type 'TypedActionCreator<string, any>'.
  }
)

Note also how in the snippet above, createAction(…).withPayload() allows us to specify the type of the payload using a type parameter - another way in which Preboiled helps you keep your code type-safe.

What about Redux Starter Kit?

About a year ago, Redux core maintainer Mark Erikson released Redux Starter Kit, a library that makes it easy to get productive in Redux. It is similar to Redux Preboiled in that it also includes a bunch of helper functions, including a createAction function that inspired the Preboiled equivalent. In fact, before I started Preboiled, I helped porting Redux Starter Kit to TypeScript.

However, the two libraries differ somewhat in their mission and approach. The vision behind Redux Starter Kit is to become a “create-react-app of Redux” - an opinionated plug-and-play Redux setup that lets you hit the ground running without wasting time on menial tasks like integrating Dev Tools or deciding on a side effects library (Redux Thunk is pre-configured). The flip side, of course, is that you might get things that are more than, or different from, what you want; for instance, Redux Starter Kit’s createReducer helper pulls in Immer for simplified immutable updates, even if you have no interest in leveraging that integration.

Redux Preboiled, on the other hand, takes more of a toolkit approach. It doesn’t help with setting up your Redux store or choosing your middleware. Instead, it just gives you set of useful tools, each of which you can use or ignore as needed. This flexibility results in a bit less convenience, but makes the libary useful to a wider set of Redux projects.

Ulimately, Redux Starter Kit and Preboiled are not really conflicting choices. You may well use the latter on top of a store set up with the former. I also believe there is a lot of room for cross-pollination, and I can see myself contributing bits of Preboiled to Starter Kit as well if they are good fits.

Ready for Serving

Version 0.1.0 of Redux Preboiled is now available on NPM and [GitHub][redux-preboiled-github]. To get started, check out the documentation, which includes several guides and also detailed API documentation for all of the helpers. Enjoy!