redux-create-fetcher

1.0.11 • Public • Published

Overview

Introduction

Redux is a state management framework for JavaScript apps. Web apps often require dynamically fetching resources. Web apps built with redux tend to request and merge resources into the redux store in a very regular pattern. This pattern can be demonstrated with a simple example:

Suppose a developer is writing a web app which has users and each user has a list of friends. If the developer wishes to dynamically fetch a list of friends associated to a given user, she will probably write three action creators---one to signify that an HTTP request was made, one to signify that a response was successfully parsed, and one to signify that an error occured. She will also export a function which serves as the API to those action creators.

const fetchFriendsRequest = user => ({
    type: 'FETCH_FRIENDS_REQUEST',
    user
})

const fetchFriendsSuccess = ({ user, friends }) => ({
    type: 'FETCH_FRIENDS_SUCCESS,
    user,
    friends
})

const fetchFriendsFailure = ({ user, error }) => ({
    type: 'FETCH_FRIENDS_ERROR',
    user,
    error
})

export const fetchFriends = user => {
    return dispatch => {

        dispatch(fetchFriendsRequest(user))

        return fetch('/api/' + user + '/friends/')
            .then(response => response.json())
            .then(
                friends => dispatch(fetchFriendsSuccess({
                    user,
                    friends
                }))
            )
            .catch(
                error => dispatch(fetchFriendsError({
                    user,
                    error
                }))
            )
    }
}

In fact, anytime the developer wishes to dynamically fetch a resource and merge it into the state, she will probably write code which follows this pattern: writing three action creators and exposing a single function as an interface to them.

This leads to lots of boilerplate. Specifically, the four functions all have the same prefix, the action creators almost always take keys or key-value pairs (in the example, keys are users and key-value pairs are {user, friends} and {user, error} objects), and the body of the exported function always has the same structure.

Solution: redux-create-fetcher

Much of the boilerplate in this paradigm can be removed. A function can be written which encapsulates the three action creators---since none of them are ever exported---and returns the exported function.

The resulting function is Fetcher, the default export of the redux-create-fetcher package.

Examples

The developer who wrote the code above could replace all of it with this code:

export const fetchFriends = Fetcher(
    'FETCH_FRIENDS',
    { fetchURLFunc: user => '/api/' + user + '/friends/' }
)

Modifying the response before dispatching a success action

Suppose the developer wants to expose a function similiar to fetchFriends, but with the caveat that only friends whose names start with the letter 'A' are packaged into the resulting success action. parseResponse solves this problem:

export const fetchFriends = Fetcher(
    'FETCH_FRIENDS',
    {
        fetchURLFunc: user => ('/api/' + user + '/friends/'),
        parseResponse: friends => friends.filter(f => f.name.startsWith('A')),
    }
)

Expecting responses which aren't JSON

Suppose the developer now expects her /api/<user>/friends/ endpoint to return data in some sort of binary format. responseType solves this problem:

export const fetchFriends = Fetcher(
    'FETCH_FRIENDS',
    {
        fetchURLFunc: user => ('/api/' + user + '/friends/'),
        responseType: 'blob',
        parseResponse: blob => friendsFromBlob(blob),
    }
)

Deferring success actions

Suppose the developer does not wish to dispatch a success action immediately upon parsing a response from her endpoint. A typical scenario would be that she wishes to create a mugshot from data associated with a user and store this created image in the redux store. This requires using the image.onload callback, rather than immediately dispatching a success action containing a response from her endpoint. Specifying parseResponse as async solves this problem:

export const fetchFriends = Fetcher(
    'FETCH_FRIENDS',
    {
        fetchURLFunc: user => ('/api/' + user + '/mugshot/'),
        responseType: 'blob',
        parseResponse: mugshotBlob => (new Promise((success, failure) => {
            let mugshotURL = URL.createObjectURL(mugshotBlob);
            let mugshot = new Image();
            mugshot.onload = () => {
                success(mugshot)
            }
            mugshot.src = mugshotURL;
        }))
    }
)

API

Fetcher takes as input an actionPrefix and options and it returns the desired fetch function.

actionPrefix

actionPrefix is a string which is the prefix of the action type associated with each of the three action creators generated by Fetcher.

options

The default value for options is

{
    fetchURLFunc,
    fetchOptionsFunc = key => ({ method: 'GET' }),
    responseType = 'json',
    parseResponse = value => value,
}

fetchURLFunc

fetchURLFunc is a required argument. It is a function which takes a key and returns the URL from which to fetch a resource.

fetchOptionsFunc

fetchOptionsFunc is a function which takes a key and returns options to pass into the call to fetch when fetching a resource.

responseType

responseType specifies the type of response to expect. Valid values are 'arrayBuffer', 'blob', 'formData', 'json', and 'text'. responseType defaults to 'json'.

parseResponse

parseResponse is typically a function which takes as input an ArrayBuffer, a Blob, a JSON Object, etc., depending on the specified responseType and returns value which is then passed into the success action creator. parseResponse defaults to the identity function.

If the user does not wish to immediately dispatch actions upon receiving a response from her endpoint, she can pass in an asynchronous function instead. For example,

export const fetchFriends = Fetcher(
    'FETCH_FRIENDS',
    {
        fetchURLFunc: user => ('/api/' + user + '/mugshot/'),
        responseType: 'blob',
        parseResponse: mugshotBlob => (new Promise((success, failure) => {
            let mugshotURL = URL.createObjectURL(mugshotBlob);
            let mugshot = new Image();
            mugshot.onload = () => {
                success(mugshot)
            }
            mugshot.src = mugshotURL;
        }))
    }
)

Package Sidebar

Install

npm i redux-create-fetcher

Weekly Downloads

1

Version

1.0.11

License

MIT

Unpacked Size

44.5 kB

Total Files

8

Last publish

Collaborators

  • fschr