Skip to content

Instantly share code, notes, and snippets.

@jasonrhodes
Last active November 20, 2017 22:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasonrhodes/788f418809284a70d48547d5a4aba824 to your computer and use it in GitHub Desktop.
Save jasonrhodes/788f418809284a70d48547d5a4aba824 to your computer and use it in GitHub Desktop.
Async Redux Hell

The Rules

  1. Action creators must return something "simple". A plain object, a simple function call, or at worst: a thunk.
  2. Internal API call logic must be encapsulated and abstracted away out of sight
    • authorization token decoration
    • refresh and retry logic
    • logout on fail logic
  3. External API calls are subject to the same async processing as internal API calls (pending, success, fail events at minimum)
  4. Internal API results trigger notifications for success and fail, according to a limited set of rules
  5. Calls can be correctly chained (i.e. failures exit the chain), with access to previous call's response

Current Attempt

// regular single call action
function updateUser(username, data) {
  const request = { url: `/users/${username}`, method: 'PUT', body: data };
  return {
    type: 'UPDATE_USER',
    async: (dispatch, getState) => helper({ request, dispatch, getState }),
    meta: {
      notify: {
        success: (result) => `${username} was successfully updated.`,
        fail: (err) => `There was a problem trying to update `${username}`
      },
      payloads: {
        success: () => ({ username })
      }
    }
  };
}
// advanced chained call action
function updateBilling(data) {
  return {
    type: 'UPDATE_BILLING',
    async: (dispatch, getState) => {
      return helper({ request: corsRequest, dispatch, getState })
        .then(({ signature }) => billingHelper({ request: billingRequest, headers: { signature }))
        .then(() => helper({ request: syncAccountRequest, dispatch, getState }))
        .then(() => helper({ request: getAccountRequest, dispatch, getState }));
    },
    meta: {
      notify: {
        success: () => 'Billing successfully updated.',
        fail: () => 'Problem updating billing.'
      },
      payloads: {
        success: (response) => ({ data })
      }
    }
  }
};
// the async dispatcher middleware listens for actions with action.async
if (action.async) {
  dispatch({ type: types.PENDING });
  action.async(dispatch, getState)
    .then((response) => {
      dispatch({ type: types.SUCCESS, payload: { response, ...meta.payloads.success(response) });
      meta.notify.success && dispatch(globalAlert({ type: 'success', message: meta.notify.success(response) }));
    })
    .catch((err) => {
      dispatch({ type: types.FAIL, payload: err });
      meta.notify.fail && dispatch(globalAlert({ type: 'error', message: message.notify.error(err) }));
    });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment