actions

Peter Gundel edited this page Jul 21, 2017 · 3 revisions

Source

Generating action creators and action types

Redux actions creators should be doomed to staying dumb from the moment of their inception. The goal of a Redux action is to notify the app of an intention to mutate the data. From the Redux docs:

Actions are plain objects describing what happened in the app, and serve as the sole way to describe an intention to mutate the data.

To apply this statement's meaning to its fullest, redux-belt provides a function called actions. Typically, in anything bigger than a mid-sized application, developers use a file in each namespace for defining action types, and another for the action creators themselves. This helps with namespacing and introduces a lot of clarity in the code. Another thing it introduces is a huge amount of boilerplate which we could diminish to its smallest possible form. Here's an example using actions:

In actions.js

import * as belt from 'redux-belt'

export const actions = belt.actions('books', ['ADD_BOOK', 'UPDATE_BOOK'])

The actions export is flat and contains both the action types as strings and the action creator functions accessible by their camel-case keys. It looks like this:

import actions from './actions'

/**
 * actions: {
 *   ADD_BOOK: 'books/ADD_BOOK',
 *   ADD_BOOK_FAILURE: 'books/ADD_BOOK_FAILURE'
 *   ADD_BOOK_SUCCESS: 'books/ADD_BOOK_SUCCESS',
 *   UPDATE_BOOK: 'books/UPDATE_BOOK',
 *   UPDATE_BOOK_FAILURE: 'books/UPDATE_BOOK_FAILURE'
 *   UPDATE_BOOK_SUCCESS: 'books/UPDATE_BOOK_SUCCESS',
 *   addBook: (an action creator for ADD_BOOK which takes only one argument)
 *   updateBook: (an action creator for UPDATE_BOOK which takes only one argument)
 * }
 */

When calling the action creator addBook, it will generate an action with the following shape:

addBook({ id: 1, title: 'The Stranger', author: 'Albert Camus' })

/**
 * => {
 *   type: 'books/ADD_BOOK',
 *   payload: {
 *     id: 1,
 *     title: 'The Stranger',
 *     author: 'Albert Camus'
 *   },
 *   meta: {
 *     success: 'books/ADD_BOOK_SUCCESS',
 *     failure: 'books/ADD_BOOK_FAILURE'
 *   }
 * }
 *
 */

When calling the action creator updateBook, it will generate an action with the following shape:

updateBook({ id: 1, stars: 5 })

/**
 * => {
 *   type: 'books/UPDATE_BOOK',
 *   payload: {
 *     id: 1,
 *     stars: 5,
 *   },
 *   meta: {
 *     success: 'books/UPDATE_BOOK_SUCCESS',
 *     failure: 'books/UPDATE_BOOK_FAILURE'
 *   }
 * }
 *
 */

Using this helper makes it mandatory to always have action creators that accept only one argument. It also (obviously) generates action creators implicitly. You might think this is a terrible idea, but here are some arguments in its favor:

  • This provides a standard for action creation and usage of payloads throughout the app. Such a standard is sometimes missing in big projects, and this can lead to some highly time-consuming development hassles.
  • You don't have to worry about how you defined your action creator. When using it, you know it will accept only one argument, and you'll be happy that you can just pass an object, or a primitive, and access it directly when you use the action somewhere else (in a reducer or in a saga).
  • If the naming convention you chose for your action types is consistent and meaningful, you'll only need to check one file to figure out how your action creators work, how they're named, and what they do.
  • Your logic is kept for the reducer and/or the saga to handle.
  • Giving your actions a namespace is trivial.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.