Redux command actions that scale without boilerplate
Tags: redux, javascript, react, opensource
Published: 2019-11-29 13:47:08

---
description: "Fixing a common anti-pattern to make scaling Redux easier and quicker"
---

<b>Should I read this post?</b>

I think that you are more likely to find value in reading this post if you are:
1. Trying to cut down on your Redux boilerplate; or
2. Interested in improving your Redux architecture or file structure; or
3. Trying to navigate Redux actions as 'commands' versus 'events'.

Key takeaways are at the [foot](#takeaways) of this post.

<br/>
<hr/>

I recently watched a recording of a great talk by [Yazan Alaboudi](https://twitter.com/YazanAlaboudi), ['Our Redux Anti Pattern: A guide to predictable scalability'](https://youtu.be/K6OlKeQRCzo?t=2626) (slides [here](https://rangle.slides.com/yazanalaboudi/deck#/)). I really love hearing and reading about people's thoughts on Redux architecture, as something I've thought a lot about.

In the talk, Yazan makes an excellent case for two points:
1. Writing Redux actions as commands<sup><a name='fn-1' href='#en-1'>1</a></sup> is an anti-pattern; and
2. A well-written Redux action should represent a business event.

In this particular post, I'm going to respond to the first of these points, with a view to discussing the second in a separate post.

Here, my core contention is this: **Redux-Leaves solves most - and perhaps all - of Yazan's 'anti-pattern' criticisms of command actions.**

I'll do this in two parts:
* Firstly, I'll outline [Yazan's case against command actions](#what-is-yazans-case-against-command-actions); and
* Secondly, I'll demonstrate [how Redux-Leaves solves those problems](#how-does-redux-leaves-solve-these-problems).

## What is Yazan's case against command actions?

I recommend watching [Yazan's own explanation](https://youtu.be/K6OlKeQRCzo?t=3182), but below I will outline my interpretation of what he says.

#### Example code

Yazan provides some examples of [command actions](https://rangle.slides.com/yazanalaboudi/deck#/31) and their [consequences](https://rangle.slides.com/yazanalaboudi/deck#/32):

##### Command Action Example (Redux setup)

  ```js
  // in scoreboardReducer.js
  const INITIAL_STATE = {
    home: 0,
    away: 0
  };

  function scoreboardReducer(state = INITIAL_STATE, action) {
    switch(action.type) {
      case "INCREMENT_SCORE": {
        const scoringSide = action.payload;
        return { ...state, [scoringSide]: state[scoringSide] + 1};
      }
      default: return state;
    }
  }

  //in crowdExcitmentReducer.js
  const INITIAL_STATE = 0;

  function crowdExcitementReducer(state = INITIAL_STATE, action) {
    switch(action.type) {
      case "INCREASE_CROWD_EXCITEMENT": return state + 1;
      default: return state;
    }
  }
  ```

##### Command Action Consequences (Component dispatching)

  ```js
  // in GameComponent
  class GameComponent extends React.Component {
    scoreGoal() {
      dispatch({ type: "INCREMENT_SCORE", scoringSide: "home"});
      dispatch({ type: "INCREASE_CROWD_EXCITEMENT"});
      // potentially more dispatches
    }
    
    render() {
      //...
    }
  }
  ```

<br />

[In a key slide, he then lays out some costs](https://rangle.slides.com/yazanalaboudi/deck#/33) that he sees in these command-oriented examples:

> #### Disadvantages of Command actions
> * does not capture business semantics
> * [actions are coupled to reducers](#actions-are-coupled-to-reducers)
> * [too many actions are firing](#too-many-actions-are-firing)
> * [unclear why state is changing](#unclear-why-state-is-changing)
> * [leads to a lot of boilerplate](#leads-to-a-lot-of-boilerplate---does-not-scale)
> * [does not scale](#leads-to-a-lot-of-boilerplate---does-not-scale)

Here are my observations on each of these (with the exception of 'business semantics', which I'll tackle in a separate post):

### Actions are coupled to reducers
I think that, when it comes to the [example code](#example-code) provided by Yazan, it's extremely fair to note that actions are coupled to reducers. The `"INCREMENT_SCORE"` action type looks entirely coupled to the `scoreboardReducer`, and the `"INCREASE_CROWD_EXCITEMENT"` looks entirely coupled to the `crowdExcitementReducer`.

This is not a good pattern because it means we have extremely low code reusability. If I want to increment something else, like the stadium audience size, I need to use *another* action type, `"INCREMENT_AUDIENCE_SIZE"`, even though the resulting change in state is going to be extremely similar.

### Too many actions are firing
Again, when it comes to Yazan's [example code](#example-code), I think it's fair to note that more actions are being dispatched in the `scoreGoal` function that *feels* necessary. If a goal has been scored, a single thing has happened, and yet we're triggering multiple actions.

This is not a good pattern because it will clog up your Redux DevTools with lots of noise, and potentially could cause some unnecessary re-renders with your Redux state updating multiple times instead of doing a single large update.

### Unclear why state is changing
I'm not convinced that this is a big problem. For me, in Yazan's [example code](#example-code), it's not too difficult for me to make the link from `scoreGoal` to `"INCREASE_SCORE"` and `"INCREASE_CROWD_EXCITEMENT"`.

To the extent that the 'why' is unclear, I think that this can be solved by [better-commented code](https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/) - which isn't a situation unique to command actions in Redux, but something that applies to all imperatively-flavoured code.

### Leads to a lot of boilerplate /  Does not scale
I think these two are both legitimate concerns (and, at core, the same concern): if, every time we decide we want to effect a new change in state, we have to decide on a new action type and implement some new reducer logic, we will quickly get a profileration of Redux-related code, and this means that it doesn't scale very well as an approach.


## How does Redux-Leaves solve these problems?

First, let's look at some example code, equivalent to the previous examples:

##### Redux-Leaves setup

  ```js
  // store.js

  import { createStore } from 'redux'
  import reduxLeaves from 'redux-leaves'

  const initialState = {
    crowdExcitment: 0,
    scoreboard: {
      home: 0,
      away: 0
    }
  }

  const [reducer, actions] = reduxLeaves(initialState)

  const store = createStore(reducer)

  export { store, actions }
  ```

##### Component dispatching

  ```js
  // in GameComponent
  import { bundle } from 'redux-leaves'
  import { actions } from './path/to/store'

  class GameComponent extends React.Component {
    scoreGoal() {
      // create and dispatch actions to increment both:
      //    * storeState.scoreboard.home
      //    * storeState.crowdExcitement
      dispatch(bundle([
        actions.scoreboard.home.create.increment(),
        actions.crowdExcitement.create.increment()
        // potentially more actions
      ]));
    }
    
    render() {
      //...
    }
  }
  ```

Here's an [interactive RunKit playground](https://runkit.com/richardcrng/soccer-game-app-with-redux-leaves) with similar code for you to test and experiment with.

Hopefully, in comparison to the [example of more typical command actions](#example-code) given by Yazan, this Redux-Leaves setup speaks for itself:
* Only one initial state and reducer to handle
* No more writing reducers manually yourself
* No more manual case logic manually yourself

I'll also now cover how it addresses each of the specific problems articulated above:
* [Actions are *no longer* coupled to reducers](#actions-are-no-longer-coupled-to-reducers)
* [Too many actions? Bundle them into one](#too-many-actions-bundle-them-into-one)
* [Extreme clarity on what state is changing](#extreme-clarity-on-what-state-is-changing)
* [Incredibly minimal boilerplate](#minimal-boilerplate)
* [Simple to scale](#simple-to-scale)

### Actions are *no longer* coupled to reducers
Redux-Leaves gives you an [`increment`](https://redux-leaves.js.org/docs/defaults/increment) action creator out-of-the-box, that [can be used at an arbitrary state path from `actions`](https://redux-leaves.js.org/docs/api/actions).

| To increment... | `create` and `dispatch` this action... |
| --- | --- |
| `storeState.crowdExcitement` | `actions.crowdExcitement.create.increment()` |
| `storeState.scoreboard.away` | `actions.scoreboard.away.create.increment()` |
| `storeState.scoreboard.home` | `actions.scoreboard.home.create.increment()` |

You get a whole bunch of other [default action creators](https://redux-leaves.js.org/docs/defaults/overview) too, which can all be effected at an arbitrary leaf of your state tree.

### Too many actions? Bundle them into one
Redux-Leaves has a named `bundle` export, which accepts an array of actions created by Redux-Leaves, and returns a *single action* that can effect all those changes in a single dispatch.


  ```js
  import { createStore } from 'redux'
  import reduxLeaves, { bundle } from 'redux-leaves'

  const initialState = {
    crowdExcitment: 0,
    scoreboard: {
      home: 0,
      away: 0
    }
  }

  const [reducer, actions] = reduxLeaves(initialState)

  const store = createStore(reducer)

  store.getState()
  /*
    {
      crowdExcitement: 0,
      scoreboard: {
        home: 0,
        away: 0
      }
    }
  */

  store.dispatch(bundle([
    actions.scoreboard.home.create.increment(7),
    actions.scoreboard.away.create.increment(),
    actions.crowdExcitement.create.increment(9001)
  ]))

  store.getState()
  /*
    {
      crowdExcitement: 9001,
      scoreboard: {
        home: 7,
        away: 1
      }
    }
  */
  ```

<br />

### Extreme clarity on what state is changing
In Yazan's [example](#example-code) of command actions, it's not obvious how the overall store state is going to be affected by the dispatches - which score is being incremented in which bit of state?

With Redux-Leaves, the [`actions` API](https://redux-leaves.js.org/docs/api/actions) means that you are extremely explicit in which state is being changed: you use a property path to the state you want to create an action at, just as you would if you were looking at your state tree and describing which bit of state that you wanted to effect.

(This isn't addressing *quite* the same point Yazan makes, which I think is asking, 'but *why* are we increasing crowd excitement?' - but, as I indicated in [discussing that point](#unclear-why-state-is-changing), I think it's the responsibility of the developer to make the *why* of a command clear through a comment, if needed.)

### Incredibly minimal boilerplate
Here's what we need to do to get our root reducer and action creators:
```js
import reduxLeaves from 'redux-leaves'

const [reducer, actions] = reduxLeaves(initialState)
```

That's it. Two lines, one of which is an import. No faffing around with writing action constants, creators or reducer `case` statements yourself.

### Simple to scale

Suppose we want to introduce some state to keep track of team names.

All we need to do is to change our initial state...

```diff
import reduxLeaves from 'redux-leaves'

const initialState = {
  crowdExcitement: 0,
  scoreboard: {
    home: 0,
    away: 0
  },
+  teams: {
+    home: 'Man Red',
+    away: 'Man Blue'
  }
}

const [reducer, actions] = reduxLeaves(initialState)
const store = createStore(reducer)
```
... and then we can straight away start dispatching actions to update that state, without having to write any further reducers, action creators or action types:

```js
store.dispatch(actions.teams.away.create.update('London Blue'))
store.getState().teams.away // => 'London Blue'
```
<br />
<hr />

## Takeaways
* You should watch [Yazan's talk](https://youtu.be/K6OlKeQRCzo?t=2626) and look at [his slides](https://rangle.slides.com/yazanalaboudi/deck#/), both are really thoughtful
* Yazan makes a [strong case](#what-is-yazans-case-against-command-actions) against how Redux command actions are typically written
* I think that [Redux-Leaves](https://redux-leaves.js.org) [solves most of those problems](#how-does-redux-leaves-solve-these-problems), if not all


<br />
<hr />

#### Endnotes

<sup><a name='en-1' href='#fn-1'>1</a></sup> An Redux action can be considered a command if it expresses intent to *do something*. Examples given by Yazan are:
```js
{ type: 'SEND_CONFIRMATION' }
{ type: 'START_BILLING' }
{ type: 'SEND_LETTER' }
{ type: 'INCREMENT_SCORE' }
{ type: 'INCREASE_CROWD_EXCITEMENT' }
```

<small>[MAIN](#fn-1)</small>