Search code examples
javascriptreactjsreduxredux-promise-middleware

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

Solution

  • 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()
                }));
        };
    };