A (slightly) new Redux action-creator pattern

(This article assumes some familiarity with the Redux library).

Go to most any redux tutorial, or the docs themselves, and you’ll see references to “action creators”. They look something like this:

function addTodo(text) {
return {
type: ADD_TODO,
text
}
}

Nothing about this indicates that this action is meant to dispatched to the redux store. Indeed, to do so, you’ll need some code that does this:

dispatch(addTodo(text))

Most of the time, this is done for you in redux’s library code (as per docs):

The dispatch() function can be accessed directly from the store as store.dispatch(), but more likely you'll access it using a helper like react-redux's connect(). You can use bindActionCreators() to automatically bind many action creators to a dispatch() function.

On the one hand, this makes the easiest use case very easy to set up — just pass { addTodo } as the second argument to connect() , and you get your addTodo() prop.

But there’s a few things with this pattern that I think make it problematic:

  1. For beginners, the connect between dispatch() and the prop method being called is completely obfuscated because the library auto-magically binds it.
  2. Any kind of asynchronous action requires a new library. redux-thunk, redux-saga, redux-promise and others are all really cool libraries, but having to choose a library, learn it, and teach it, all to do something that is fairly straightforward in vanilla javascript… well it’s not great.
  3. One fairly common pattern is to want a React Component to call a method passed in with certain of its other props:
const Item = ({ onSelect, item }) => (
<li onClick={() => onSelect(item.id)}>{item.name}</li> )

But this requires the component itself knowing about what arguments to pass into the onSelect method. It’s a lot nicer to bind that outside of the component, and just be able to do:

const Item = ({ onSelect, item }) => (
<li onClick={onSelect}>{item.name}</li>
)

This makes your component a lot easier to read, and makes it easier to keep any changes to onSelect (maybe you decide you want to pass in the whole item , or maybe also pass in a new index prop and pass that to the action as well) outside of the visual rendering.

Doing this using the redux connect() method is hard. mapDispatchToProps will accept props as a second argument, but now you need to do all the binding yourself. Additionally, if you are just using the vanilla connect() , that method will trigger every time new props are passed into your Component, even if they don’t affect your actions (new changes to the library have made it possible to optimize this, but that’s not a very beginner-friendly approach).

Is there a better way?


Enter recompose, a helper library for building higher-order components. There’s a lot of really great articles on recompose, and if you’re not familiar with it, I’d highly recommend reading some. It’s great.

As relevant here, it ships a withHandlers enhancer. It looks like this:

const enhancer = withHandlers({
onSelect: props => () => props.onSelect(props.item.id, props.index)
})

With object destructuring, we can make this look even nicer:

const enhancer = withHandlers({
onSelect: ({ onSelect, item, index }) => () => onSelect(item.id, index)
})

Our List component can now be enhanced, and it will receive its onSelect method that can simply be called, and will appropriately be bound to the specific props. The actual rendering no longer needs to be concerned with the specifics.


So how does this fit in with redux?

const listEnhancer = connect(state => ({ items: state.items }))
const List = listEnhancer(({ items, dispatch }) => (
<ul>
{ items.map((item, index) =>
<Item
key={item.id}
item={item}
index={index}
dispatch={dispatch} />
) }
</ul>
)

And now we only need to slightly change our Item enhancer from above:

const enhancer = withHandlers({
onSelect: ({ dispatch, item, index }) => () => {
dispatch({
type: SELECT_ITEM,
index,
item_id: item.id
})
}
})
const Item = enhancer(({ onSelect, item }) => (
<li onClick={onSelect}>{item.name}</li>
)

(I’m not always a huge fan of the Flux Standard Action format, but feel free to put index and item_id into a payload or meta ).

Some advantages:

  1. Very explicit about calling dispatch — no magic.
  2. withHandlers automatically passes static methods to you: no time wasted re-creating new methods, and you can implement shouldComponentUpdate (if needed) without worrying about receiving new methods whenever item or index changes. The passed methods simply look those props up at execution time.
  3. No need for a separate action creators file if you’re only dispatching the action in one place. If you need to re-use the actions, you can always separately export something like:
export const dispatchOnSelect = ({ dispatch, item, index }) = {
dispatch({
type: SELECT_ITEM,
index,
item_id: item.id
})
}

(Note that this is actually calling dispatch, unlike our action creators from above, so there’s no extra steps required).

And then our enhancer:

import { dispatchOnSelect }
const enhancer = withHandlers({
onSelect: ({ dispatch, item, index }) => () => dispatchOnSelect({ dispatch, item, index })
})

This also has the advantage that if some components want to explicitly call onSelect with the item and index (for example if we wanted to put in a button elsewhere on the page that selects the “Most Recently Viewed” item), we could simply pass a different enhancer while still using the same dispatchOnSelect .


The real win, though, is for async actions — no need for any other library!

(After some discussions, I think this should be amended to read: “The real win though, is being able to do whatever you want in your methods. Async actions, dispatching multiple actions, make a call to log some user action… the world is your oyster! Need for middleware or other libraries, unless you really need them!”)

Here’s something that covers the use cases that redux-thunk or redux-promise might provide for you.

const enhancer = withHandlers({
createTodo: ({ dispatch }) => todo => {
dispatch({ type: CREATE_TODO, todo })
    return callCreateTodoAPI(todo)
.then(createdTodo => {
dispatch({
type: CREATE_TODO_SUCCESS,
createdTodo
})
// Optionally: return createdTodo
})
.catch(error => {
dispatch({
type: CREATE_TODO_ERROR,
error
})
// Optionally: throw error
})
}
})
const TodoInput = enhancer(({ onCreate }) => //... 

Maybe you want to return the promise, so that the calling component can do this.props.createTodo().then(this.resetForm()) .

Maybe you want to re-throw the error, so that your calling component can show a toast message.

Maybe you just want to catch the error, and store that state in your store.

Totally up to you — no need to find a library that supports your use case. No magic, no fuss.