How can I merge multiple axios calls into one payload using redux and redux-promise?

1.7k Views Asked by At

I'm trying to make one action that will make two separate API calls and then merge the data together to then pass to my reducer. I tried making the first call with a promise, and then merging the data into it with axios's transformResponse, but I keep getting a Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. error.

What's the best way to do this?

1

There are 1 best solutions below

0
On

Importantly, reducers are always synchronous. So a reducer shouldn't receive a promise. Actions should only consist of plain data and take the following form:

{
  type: 'ADD_TODO',
  payload: {
    text: 'Do something.'  
  }
}

redux-promise allows us to put a promise in the payload of an action.

Once redux-promise is added to the middleware chain we can dispatch actions which have a promise in their payload property.

The redux-promise middleware will then handle this action before it reaches the store and dispatches any further actions once the promise has been resolved or rejected and the payload will consist of the response or error in each case.

These further actions that are dispatched by redux-promise once the promise has been resolved or rejected are synchronous just like any action that is dispatched. Remember the job of our middleware is to dispatch additional actions after the promise in the action payload has resolved or rejected.

Dispatch Action --> Middleware (redux-promise dispatches further actions) --> reducers get the error or data from these further actions --> Store updated by reducers

Here is a self-contained example where we will make a request for data on a movie to the open movie database api to demonstrate how redux-promise works.

On to the store first

store.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import promiseMiddleware from 'redux-promise'

let rootReducer = combineReducers(reducers)

let store = createStore(
  rootReducer,
  applyMiddleware(promiseMiddleware)
)

Now we need pass Redux's applyMiddleware function as an argument to create store and in turn provide redux-promise as an argument to applyMiddleware.

Now the action creator.

When the action creator function is called below the axios request will be made. This request will return a promise which will then be passed through our redux-promise middleware.

actions/index.js

export function requestMovie(movieTitle) { 
    // create the url for our api request
    const url = `http://www.omdbapi.com/??=${movieTitle}&y=&plot=short&r=json`

    return { // return our action
        type: 'REQUEST_MOVIE',
        payload: axios.get(url)  // this .get method returns a promise
     }
}

Our reducer will then be given the the result of our promise.

The main function handles the action that is dispatched by the redux-promise middleware after a promise has been rejected or resolved

reducers/index.js

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
    movies: movies,
    err: err
});

function err(err = false, action){
    switch (action.type) {
       case 'REQUEST_MOVIE':
        return(action.payload.response.status !== 200)
    }

    return err;
}

function movies(movies = [], action){
    switch (action.type) {
        case 'REQUEST_MOVIE':
          //  if promise was not rejected the data property on our action payload will exist comprising the movie data that we requested
          if(action.payload.response.status === 200){
             return [...movies, action.payload.response.data]
            } else{
                return [movies]
            }
    }

    return movies
}

export default rootReducer;