Skip to content

Instantly share code, notes, and snippets.

@thomasboyt
Last active November 30, 2016 19:55
Show Gist options
  • Select an option

  • Save thomasboyt/ca8edefb0ef07b8ea9077f50d5464575 to your computer and use it in GitHub Desktop.

Select an option

Save thomasboyt/ca8edefb0ef07b8ea9077f50d5464575 to your computer and use it in GitHub Desktop.

redux "request" idea

(this is adapted from the redux-happy-async project I made earlier this year)

Using Request State

A "request" is simply a tiny little state machine that lives in a request reducer. It's keyed off of a name (usually an action type) and, optionally, a unique key to help track multiple requests of the same type.

By keeping a request in your Redux state, you can easily trigger a request (through an action creator) in a component and react to the loading/error states of that request in a different component. It is also easy to prevent multiple, identical requests from being in-flight at once.

The request holds the following state:

  • status: This is either REQUEST_IDLE, REQUEST_PENDING REQUEST_SUCCESS, or REQUEST_ERROR.
  • error: This is an error value supplied by the action creator when the error state is entered.
  • key: This is the unique key of the request, if supplied.

Within a component, you can fetch a request from the state:

import {getRequest} from 'LIB_NAME';

function mapStateToProps(state, props) {
  return {
    // gets the request for actionTypes.getUser with the unique key props.userId
    request: getRequest(state, actionTypes.getUser, props.userId);
  };
}

Creating Requests

Requests are created in action creators:

import {requestStart, requestError, requestSuccess} from 'LIB_NAME';

export function getUser(userId) {
  return async (dispatch) => {
    dispatch(requestStart(actionTypes.getUser, userId));

    const resp = await window.fetch(/* ... */);

    if (resp.status !== 200) {
      const err = await resp.json();

      dispatch(requestError(actionTypes.getUser, userId, error));
      return;
    }

    dispatch(requestSuccess(actionTypes.getUser, userId));

    const data = await resp.json();

    dispatch({
      type: actionTypes.getUser,
      user: data,
    });
  };
}

If you attempt to start an already-started request, an error will be thrown.

Resetting Requests

To reset a request - for example, to ensure that when you exit and return to a page, you do not see state from a past request - simply use:

import {requestReset} from 'LIB_NAME';
dispatch(requestReset(actionTypes.getUser, userId));

TODO: can an in-flight request be reset?

FAQ

Can my own reducers handle the request* action creators?

They potentially could, but a better way to handle custom state updating would be to simply dispatch another action if an error is encountered.

How can I have multiple instances of a request if there is no obvious unique key to use?

You can create a counter in your action creator:

let todoRequestId = 0;
export function createTodo(text) {
  return async (dispatch) => {
    dispatch(requestStart(actionTypes.createTodo, todoRequestId));
    // ...
  };
}

You'll likely want to then get a list of pending requests to be able to reference/display these requests (see below).

How can I manage pending requests of a given type?

You can filter for requests from the async reducer:

const createTodoRequests = store.getState().requests[actionTypes.createTodo];
const pending = createTodoRequests.map((request) => request.status === REQUEST_PENDING);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment