Smajl
Smajl

Reputation: 7995

How do I chain actions with Redux Promise Middleware?

I am quite new to React and Redux. I want to chain multiple API calls using redux-promise-middleware and implemented my actions as follows:

locationActions.js:

import { visitLocation } from '../services/actionService';

export const VISIT_LOCATION = 'VISIT_LOCATION';
export const VISIT_LOCATION_PENDING = 'VISIT_LOCATION_PENDING';
export const VISIT_LOCATION_FULFILLED = 'VISIT_LOCATION_FULFILLED';
export const VISIT_LOCATION_REJECTED = 'VISIT_LOCATION_REJECTED';

const visitLocationAction = (characterId, location) => ({
    type: VISIT_LOCATION,
    // visitLocation returns a new promise
    payload: visitLocation(characterId, location)
});

export { visitLocationAction as visitLocation };

I dispatch this action from my React component using mapDispatchToProps:

const mapDispatchToProps = dispatch => (
    bindActionCreators({ visitLocation }, dispatch)
);

This works! However, I want to dispatch another action after visitLocation is settled and fulfilled. I can not call Promise.then() because it doesn't provide the dispatch method, therefore not "binding" my action to the reducer.

Tutorials mention I should call dispatch(secondAction()) but I do not have dispatch available in my action creators.

Can someone point out to me what am I missing?

Edit:

As suggested in the first answer, I tried the following approach:

import { visitLocation } from '../services/locationService';
import { fetchSomething } from '../services/otherService';

const visitLocationAction = (characterId, location) => {
    return (dispatch) => {
        return dispatch(visitLocation(characterId, location))
            .then(() => dispatch(fetchSomething()));
    };
};

export { visitLocationAction as visitLocation };

But I got action:undefined and the following error:

Error: Actions must be plain objects. Use custom middleware for async actions.
    at dispatch (redux.js:200)
    at redux-logger.js:1

Upvotes: 1

Views: 7327

Answers (2)

Patrick Burtchaell
Patrick Burtchaell

Reputation: 4132

Tutorials mention I should call dispatch(secondAction()) but I do not have dispatch available in my action creators.

Let me answer this part of your question first. As mentioned in the first answer, you need Redux Thunk, a middleware, to use dispatch in your action creators.

By default, Redux action creators return plain objects like this:

const returnsAnObject = () => ({
  type: 'MY_ACTION'
  payload: ...,
  meta: ....
})

With Redux Thunk, action creators can essentially return functions. A function that returns a function is called a "thunk", hence the name of Redux Thunk.

const returnsAFunction = (dispatch) => dispatch({
  type: 'MY_ACTION'
  payload: ...,
  meta: ....
})

In the specific case of Redux Thunk, you return the dispatch function. Redux Thunk enables you to return dispatch by providing it as a parameter to your action creators.

But I got action:undefined

Let's unpack this. We know--thanks to Redux Thunk--you can chain multiple actions together using dispatch. However, as you mentioned in your edited question, you still get the "plain object" error.

Error: Actions must be plain objects. Use custom middleware for async actions.
    at dispatch (redux.js:200)
    at redux-logger.js:1

This is because you called dispatch with a Promise object, not a plain object.

const visitLocationAction = (characterId, location) => {
    return (dispatch) => {
        return dispatch(visitLocation(characterId, location))

            // Yes, it is correct to call dispatch here
            // However, `dispatch` accepts plain objects, not Promise objects
            .then(() => dispatch(fetchSomething()));
    };
};

To fix this problem, simply modify your second dispatch to use a plain object:

const visitLocationAction = (characterId, location) => {
    return (dispatch) => {
        return dispatch(visitLocation(characterId, location))
            .then(() => dispatch({
                type: 'VISIT_LOCATION_SECOND_TIME'
                payload: fetchSomething()
            }));
    };
};

Upvotes: 3

Pavlo Kozlov
Pavlo Kozlov

Reputation: 1101

As mentioned in the documentation for redux-promise-middleware, The middleware can be combined with Redux Thunk to chain action creators.

So you can write your action like:

const secondAction = (data) => ({
  type: 'SECOND',
  payload: {...},
})

const thirdAction = (data) => ({
  type: 'THIRD',
  payload: {...},
})

const firstAction = () => {
  return (dispatch) => {
    return visitLocation(characterId, location)
      .then((data) => dispatch(secondAction(data)))
      .then((data) => dispatch(thirdAction(data)))
  }
}

or use async/await

const secondAction = (data) => ({
      type: 'SECOND',
      payload: {...},
    })

    const thirdAction = (data) => ({
      type: 'THIRD',
      payload: {...},
    })

    const firstAction = async () => {
      return (dispatch) => {
        const data = await visitLocation(characterId, location));
        dispatch(secondAction(data))
        dispatch(thirdAction(data))
      }
    }

and then use it as you mentioned:

const mapDispatchToProps = dispatch => (
  bindActionCreators({ firstAction }, dispatch)
);

Upvotes: 1

Related Questions