redux-hammock

0.3.3 • Public • Published

Redux-Hammock

Build Status

Hammock is a library which allows you to abstract away some details and boilerplate when using redux and fetch with REST endpoints. You just write a little configuration object and hammock generates actions and a reducer for you!

Motivation

hammock is designed to abstract away the boilerplate involved in using fetch and redux-thunk to manage communication with a REST API from a JS frontend application. Consider the following (trivial) example of a user API:

const fetchUserInfo = id => fetch(`/api/v0/user/${id}`)

const REQUEST_GET_USER = 'REQUEST_GET_USER'
const requestGetUser = () => ({ type: REQUEST_GET_USER })

const RECEIVE_GET_USER_SUCCESS = 'RECEIVE_GET_USER_SUCCESS'
const receiveGetUserSuccess = response => (
  { type: RECEIVE_GET_USER_SUCCESS, payload: response }
)

const RECEIVE_GET_USER_FAILURE = 'RECEIVE_GET_USER_FAILURE'
const receiveGetUserFailure = response => (
  { type: RECEIVE_GET_USER_FAILURE, payload: response }
)

const getUser = id => {
  return dispatch => {
    dispatch(requestGetUser())
    return fetchUserInfo(id).then(
      response => dispatch(receiveGetUserSuccess(response)),
      response => dispatch(receiveGetUserFailure(response))
    )
  }
}

const INITIAL_USER_STATE = {
  userInfo: {},
  fetchStatus: undefined,
}

const userReducer = (state = INITIAL_USER_STATE, action) => {
  switch (action.type) {
    case REQUEST_GET_USER:
      return Object.assign({}, state, { fetchStatus: 'PROCESSING' })
    case RECEIVE_GET_USER_SUCCESS:
      return Object.assign({}, state, { fetchStatus: 'SUCCESS', userInfo: action.payload })
    case RECEIVE_GET_USER_FAILURE:
      return Object.assign({}, state, { fetchStatus: 'FAILURE' })
    default:
      return state
  }
}

Simple enough, right? We have some actions for indicating the request is in-flight, one for success, one for failure. When we want to make a request, we call dispatch(getUser(id)), and if the request goes through (i.e. if the fetch call resolves) we'll get our user information in our store, so we can display in a React view layer or similar.

This works, and is actually fine, but consider the case where we have four different APIs we need to communicate with, or ten, or fifteen. We'll have almost this exact same set of functions, with associated nearly identical tests, for each API. Yuck! That's a lot of boilerplate. We can cut down a little bit on this using utilities like redux-actions, but we've still got to write down all that code for each reducer.

If we look a little closer, we can see that the only parts of this whole setup which are particular to this API are the URL and the name. So wouldn't it be nice if, instead, we could write something like:

const userEndpoint = {
  name: 'user',
  verbs: [ 'GET' ],
  getUrl: id => `/api/v0/user/${id}`
}

Coincidentally this is exactly what hammock lets you do! Great!

Installation

To install hammock you can just do:

npm install --save redux-hammock

hammock assumes that you already have your reducer set up to use the redux-thunk middleware, as described here.

Usage and API

As hinted above, to use hammock you declare an object (of type Endpoint) which is used to automatically generate action types, action creators, a reducer, and a redux-thunk async action creator which wraps a call to fetch (along the lines of the example above).

Endpoint declaration

There a few properties that are a required, and a few optional ones which can be used if more customization is required.

name

String: the name for the API, used to name the reducer and also any derived actions.

verbs

[String]: the HTTP verbs the endpoint supports. Currently GET, PATCH, and POST are supported.

${verb}Url

String|Function: This is either a string, or a function which returns a string, which is used to get the URL to fetch for a particular HTTP verb (e.g. { getUrl: '/api/v0/example' }). If a function is provided, the arguments passed to the async action creator are passed through, making it easy to use with a REST API to GET or PATCH a particular record.

Example:

const getEndpoint = {
  name: 'user',
  verbs: [ 'GET' ],
  getUrl: id => `/api/v0/user/${id}`
}

${verb}Func (optional)

Function: This is a function which is used to issue a verb request. If this option is passed, the ${verb}Url parameter is no longer necessary. This may be useful if you need to do some specialized error handling for a particular endpoint, or if special editing needs to happen immediately before the request is issued.

fetchFunc (optional)

Function: If a nonstandard fetch implementation or wrapper is necessary (for instance to deal with CSRF tokens or similar) a fetchFunc can be passed, which will be used instead of window.fetch.

initialState (optional)

Object: by default hammock will generate a sensible default empty state for you. This property can be used if you want custom control, for instance you need a property other than those defined on the RestType type, or you need to use a custom starting placeholder value (like an empty string instead of undefined).

Generating actions and reducers

hammock exports two functions (deriveReducers and deriveActions) which both take an Endpoint and return and reducer and an object holding action creators, respectively. The reducer can be treated like a normal Redux reducer, combined with others using combineReducers, etc. The actions object will have a property for each supported HTTP verb defined on it. For our user example above, this would look like:

// fetch a user object
dispatch(userActions.get(userId))

The async action creator function also has the relevant action types defined on it as properties, at actions.${verb}.requestType, successType, and failureType, as a convenience for testing and debugging. Again, for our user example this would look like:

userActions.get.requestType // REQUEST_GET_USER
userActions.get.successType // RECEIVE_GET_USER_SUCCESS
userActions.get.failureType // RECEIVE_GET_USER_FAILURE

It's up to you how to organize the created actions. On strategy is to stick them all on one object, under the same keys as the stores, so that you just import an actions object in your redux container files and do something like actions.user.get(userId). Another option would be to declare the actions alongside the reducers, in separate files. It's up to you!

Readme

Keywords

Package Sidebar

Install

npm i redux-hammock

Weekly Downloads

122

Version

0.3.3

License

BSD-3-Clause

Unpacked Size

26.4 kB

Total Files

6

Last publish

Collaborators

  • abeglova
  • cchudzicki
  • mitx-devops
  • rhysyngsun
  • gumaerc
  • aliceriot