npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-hammock

v0.3.3

Published

A library for simplyfing the use of redux-thunk with REST APIs

Downloads

84

Readme

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!