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-requests

v1.0.2

Published

Manages in-flight requests with a Redux reducer - avoid issuing duplicate requests without any special logic!

Downloads

1,500

Readme

redux-requests Version

Manages in-flight requests with a Redux reducer - avoid issuing duplicate requests without any special logic!

npm install --save redux-requests

Live Example!

Avoiding the issue of multiple requests

Say your application has two views for the same set of data, and this data has not yet been fetched. A naïve approach to fetch this data would be to trigger an Action Creator, which fetches the data from an HTTP API endpoint, in both of the views as soon as they render (componentWillMount in React terms).

The problem with this approach is that you end up with two identical HTTP requests when you only need one! You waste bandwidth doing this, and you may also waste render cycles as the Store updates twice as a result of handling both identical responses.

How can we fix this?

You could wrap all your calls to fetch the data with if statements, and keep track of that state somewhere, but who wants to do that by hand?

Enter: redux-requests

This library will not only keep track of all pending requests for you, but also provide a convenient middleware function that will avoid dispatching Actions to request data if there is already a pending HTTP request for this data in flight!

As a result, you can use the very same naïve approach outlined earlier with hardly any code changes and it will "just work"! Keep your views stateless and your Reducers ignorant of the notion of "pending requests"!

Simple example

Just specify a function that makes the request (should return a Promise), Action objects to dispatch depending on the outcome of the request, and register the createRequestMiddleware middleware and the requestsReducer reducer as part of your Redux configuration. That's it.

import { attemptRequest, requestsReducer, createRequestMiddleware } from 'redux-requests';
// Attempt to make a request if there isn't one for this URL already
function loadRepos(userId) {
  // Using redux-thunk middleware here, but other options should work as well
  return function (dispatch, getState) {
    const url = `https://api.github.com/users/${userId}/repos`;

    attemptRequest(url, {
      begin: () => ({
        type: 'LOAD_REPOS',
        payload: {
          userId
        }
      }),
      success: response => ({
        type: 'LOAD_REPOS',
        payload: {
          userId,
          response
        }
      }),
      failure: error => ({
        type: 'LOAD_REPOS',
        error,
        payload: {
          userId
        }
      })
    }, () => fetch(url)
      .then(checkStatus)
      .then(parseJSON)
    , dispatch);
  }
}
// Add additional reducer and middleware
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createRequestMiddleware())(createStore);
let store = createStoreWithMiddleware(combineReducers({ requestsReducer, githubRepos }));

What's going on: before and after

The attemptRequest function is actually just a simple helper (and is completely optional). All it does is:

  1. Add meta.httpRequest fields to your Action objects
  • meta.httpRequest.url is required, and will be used as the unique identifier for the request
  • meta.httpRequest.done is a boolean indiecating if this action corresponds to a beginning or ending part of the request sequence
    • Typically a successful response Action and a failed response Action will both have meta.httpRequest.done = true
  1. Check if the dispatch for your initial request Action was cancelled (dispatch will return undefined), and if so, prevent issuing the request

Original, naïve code (without redux-requests library):

// React component
class Repos extends Component {
  constructor(props) {
    super(props);
  }

  componentWillMount() {
    // Action Creator attempts to request data for this user
    this.props.loadRepos(this.props.username);
  }

  render() {
    return (
      <div>
        { this.props.repos }
      </div>
    );
  }
}

function mapStateToProps(state)  {
  return {
    repos: state.githubRepos
  };
}

function mapDispatchToProps(dispatch) {
  return {
    loadRepos: (userId) => { dispatch(loadRepos(userId)); }
  };
}

export const ReposComponent = connect(mapStateToProps, mapDispatchToProps)(Repos);

// Action Creator
export function loadRepos(userId) {
  return function (dispatch, getState) {
    const url = `https://api.github.com/users/${userId}/repos`;

    dispatch({
      type: 'LOAD_REPOS',
      payload: {
        userId
      }
    });

    fetch(url)
      .then(response => dispatch({
        type: 'LOAD_REPOS',
        payload: {
          userId,
          response
        }
      }))
      .catch(error => dispatch({
        type: 'LOAD_REPOS',
        error: true,
        payload: {
          userId,
          error
        }
      })
    );
  }
}

// Store
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
let store = createStoreWithMiddleware(combineReducers({ githubRepos }));

New code (using redux-requests to manage pending requests):

// React component stays exactly the same!

// Action Creator changes slightly
export function loadRepos(userId) {
  return function (dispatch, getState) {
    const url = `https://api.github.com/users/${userId}/repos`;

    if (!dispatch({
      type: 'LOAD_REPOS',
      payload: {
        userId
      },
      meta: {
        // Add metadata to the action
        httpRequest: { url, done: false }
      }
    })) {
      return; // bail out here if the middleware cancelled the dispatch
    }

    fetch(url)
      .then(response => dispatch({
        type: 'LOAD_REPOS',
        payload: {
          userId,
          response
        },
        meta: {
          // Add metadata to the action
          httpRequest: { url, done: true }
        }
      }))
      .catch(error => dispatch({
        type: 'LOAD_REPOS',
        error: true,
        payload: {
          userId,
          error
        },
        meta: {
          // Add metadata to the action
          httpRequest: { url, done: true }
        }
      })
    );
  }
}

// Add additional reducer and middleware
import { requestsReducer, createRequestMiddleware } from 'redux-requests';
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createRequestMiddleware())(createStore);
let store = createStoreWithMiddleware(combineReducers({ requestsReducer, githubRepos }));

API

requestsReducer(state, action)

A reducer that keeps track of pending request state. It only operates on actions containing the meta.httpRequest field.

createRequestMiddleware(stateSelectorFunction)

Returns a middleware function to pass to applyMiddleware. Optionally pass a stateSelectorFunction which returns where the requestsReducer keeps its state in the Store (if not passed, will default to state => state.requests).

Ex: applyMiddleware(createRequestMiddleware(state => state.pendingHttpRequests))(createStore)

attemptRequest(url, actions, makeRequest, dispatch)

Helper function to reduce boilerplate when issuing a request, while still allowing full control over the way in which the request is made/handled.

  • url is the unique URL for this request.
  • actions should be an object with begin, success, and failure methods; each of which return an Action object (but do not need to include the meta.httpRequest information, as that will be added automatically).
  • makeRequest should return a Promise (how you make/handle the request is up to you).
  • dispatch is function called when an Action is triggered (typically this will be the standard Redux store's dispatch method).

Credits

Inspired by the Marty fetch API.