Redux Thunk Utils

May 30, 2019

Heads up: this post is from 2019 and its content is outdated. It is kept here for historical reference.

This is a piece of code that was useful to me while I was using redux and redux-thunk@1.x. Some people found it useful when I showed it to them, so I am sharing it here.

It is basically a shortcut for writing actions and async API requests/actions.

// Action shortcut
export function makeActionCreator(type, ...argNames) {
  let action = function (...args) {
    let action = { type }
    argNames.forEach((arg, index) => {
      action[argNames[index]] = args[index]
    })
    return action
  }

  return {
    action,
    type,
  }
}

// Usage
export const ENTER_CHAT_ROOM = makeActionCreator("ENTER_CHAT_ROOM", "room")

With our action generator utility, we further extend it for redux-thunk usage.

A common pattern arose most of the times I used thunk, so I abstracted it into a function. This way I had to write less code, but if needed, the action could be further extended by chaining 'thens' and 'catches' on the returned promise.

export function asyncCreator(api, actionName, resourceName) {
  let actionNames = ["REQUEST", "SUCCESS", "FAILURE"].map(
    stage => actionName + "_" + stage
  )
  let request = makeActionCreator(actionNames[0], "parameters").action
  let success = makeActionCreator(actionNames[1], resourceName).action
  let failure = makeActionCreator(actionNames[2], "error").action

  let action = function (...parameters) {
    return function (dispatch) {
      dispatch(request(...parameters))

      return api(...parameters)
        .then(function (response) {
          dispatch(success(response.data))
        })
        .catch(function (error) {
          dispatch(failure(error))
          throw error
        })
    }
  }

  return {
    REQUEST: actionNames[0],
    SUCCESS: actionNames[1],
    FAILURE: actionNames[2],
    request,
    success,
    failure,
    action,
  }
}

// actions.js - Usage

const fetchUsersApi = fetch("/users")

export const fetchUsers = asyncCreator(fetchUsersApi, "FETCH_USERS", "users")

// reducer.js

import { combineReducers } from "redux"
import fetchUsers from "./actions"

function loading(state = false, action) {
  switch (action.type) {
    case fetchUsers.REQUEST:
      return true
    case fetchUsers.SUCCESS:
    case fetchUsers.FAILURE:
      return false
    default:
      return state
  }
}

function error(state = "", action) {
  switch (action.type) {
    case fetchUsers.FAILURE:
      return action.error
    default:
      return state
  }
}

function users(state = [], action) {
  switch (action.type) {
    case fetchUsers.SUCCESS:
      return action.users
    default:
      return state
  }
}

export default combineReducers({
  loading,
  error,
  users,
})

These helper functions made my life with redux and redux-thunk much easier, as long declaration files became short ones.

Hope this can also help you out!