Search code examples
asynchronouspromisereduxredux-api-middleware

Chaining asynchronous actions with redux-api-middleware


I have a React app using Redux, redux-thunk, react-router and redux-api-middleware.

In order for a Page component in my app to render, I must access two API endpoints and store their results in the redux store.

Currently, I am dispatching both actions at the same time. This leads to problems when the results of one action complete before the other. So I need to make sure one action completes before I dispatch the second one.

After reading all the documentation I am having trouble understanding how to make this happen with redux-api-middleware.

This is my current component:

class Page extends React.Component {
  static prepareState = (dispatch: Function, match: Match) => {
    const { params: { projectSlug, menuSlug, pageSlug, env } } = match
    if (!projectSlug || !env) {
      return
    }

    dispatch(getSettings(projectSlug, env))
    dispatch(getPage(projectSlug, env, menuSlug || '', pageSlug || ''))            

  }
  ...

I have tried:

class Page extends React.Component {
  static prepareState = (dispatch: Function, match: Match) => {
    const { params: { projectSlug, menuSlug, pageSlug, env } } = match
    if (!projectSlug || !env) {
      return
    }

    dispatch(
     dispatch(getSettings(projectSlug, env))
   ).then(res => {
     console.log('success', res)
     dispatch(getPage(projectSlug, env, menuSlug || '', pageSlug || ''))
   }).catch(e => {
     console.log('failure', e, menuSlug, pageSlug)
   })

  }
  ...

But this leads to the catch being called instead of the then. However the GET_SETTINGS_SUCCESS action is in fact completed, so I am confused as to why the catch is being called at all. What am I doing wrong?

This is the action generator:

export const getSettings = (projectSlug: string, env: string) => (dispatch: Function, getState: Function) => {
  if (getState().settings[env] && loadCompleted(getState().settings.apiState)) {
    // already loaded
    return
  }

  dispatch({
    [CALL_API]: {
      endpoint: settingsUrl(projectSlug, env),
      method: 'GET',
      credentials: 'include',
      types: [{
        type: 'GET_SETTINGS',
        meta: {projectSlug, env}
      }, {
        type: 'GET_SETTINGS_SUCCESS',
        meta: {projectSlug, env}
      }, {
        type: 'GET_SETTINGS_FAIL',
        meta: {projectSlug, env}
      }]
    }
  })
}

Solution

  • Create a third action called getSettingsThenPage.

    export const getSettingsThenPage = (
      projectSlug,
      env,
      menuSlug = "",
      pageSlug = ""
    ) => dispatch => {
      return dispatch(getSettings(projectSlug, env)).then(() => {
        return dispatch(getPage(projectSlug, env, menuSlug, pageSlug));
      });
    };
    

    Invoke that action wherever you must.