Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

chaining multiple async dispatch in Redux

I am trying to chain multiple actions together in the following fashion:

A. post user data to database

B. use posted data to query Elasticsearch for results

(I do A and B in parallel)

B1. with results from ES, query original database for results from two tables B2. navigate to new page and update UI

I am using thunks right now to reason about my code, but I also found this async pattern to be extremely verbose:

export function fetchRecipes(request) {
  return function(dispatch) {
    dispatch(requestRecipes(request))    
    return fetch(url)
      .then(response => response.json())
      .then(json => dispatch(receiveRecipes(request, json))
    )
  }
}

this, along with "requestRecipes" and "receiveRecipes" as other action creators seems like quite a bit just to make one async call. (a request, a receive, and a fetch function)

summary: when you're chaining 2-3 async actions whose outputs depend on each other (I need to promisify when possible), is there a more efficient means of doing so without writing 3 functions for each async call?

I figure there had to be a way. I'm pattern matching off of the Redux docs and soon became very overwhelmed with the functions I was creating

thanks a lot for the feedback!

like image 469
Anthony Chung Avatar asked Apr 17 '16 20:04

Anthony Chung


People also ask

Can I dispatch multiple actions Redux?

Well, no matter what you pass to dispatch , it is still a single action. Even if your action is an array of objects, or a function which can then create more action objects!

Is Redux dispatch asynchronous?

Introduction. By default, Redux's actions are dispatched synchronously, which is a problem for any non-trivial app that needs to communicate with an external API or perform side effects. Redux also allows for middleware that sits between an action being dispatched and the action reaching the reducers.

How do I use async dispatch in Redux?

Redux Async Data Flow​ Just like with a normal action, we first need to handle a user event in the application, such as a click on a button. Then, we call dispatch() , and pass in something, whether it be a plain action object, a function, or some other value that a middleware can look for.

What are typical middleware choices for handling asynchronous calls in Redux?

Thunk Middleware Redux thunk is a very easy way of introducing a middleware to Redux. It's even mentioned as the solution for async operations in Redux in the official redux documentation. Redux Thunk does the following. Allows writing action creators that return a function instead of an action.


2 Answers

You can use redux-saga instead of redux-thunk to achieve this more easily. redux-saga lets you describe your work using generators and is easier to reason about.

The first step is to describe how you pass your data to redux without worrying about services or async stuff.

Actions

// actions.js
function createRequestTypes(base) {
  return {
    REQUEST: base + "_REQUEST",
    SUCCESS: base + "_SUCCESS",
    FAILURE: base + "_FAILURE",
  }
}

// Create lifecycle types on `RECIPES`
export const RECIPES = createRequestTypes("RECIPES")

// Create related actions
export const recipes = {
  // Notify the intent to fetch recipes
  request: request => ({type: RECIPES.REQUEST, request})
  // Send the response
  success: response => ({type: RECIPES.SUCCESS, response})
  // Send the error
  error: error => ({type: RECIPES.FAILURE, error})
}

Reducer

// reducer.js
import * as actions from "./actions"

// This reducer handles all recipes
export default (state = [], action) => {
  switch (action.type) {
    case actions.RECIPES.SUCCESS:
      // Replace current state
      return [...action.response]

    case actions.RECIPES.FAILURE:
      // Clear state on error
      return []

    default:
      return state
  }
}

Services

We also need the recipes API. When using redux-saga the simplest way to declare a service is to creating a (pure) function which reads the request as argument and returns a Promise.

// api.js
const url = "https://YOUR_ENPOINT";

export function fetchRecipes(request) {
  return fetch(url).then(response => response.json())
}

Now we need to wire actions and services. This is where redux-saga come in play.

// saga.js
import {call, fork, put, take} from "redux-saga/effects"
import * as actions from "./actions"
import * as api from "./api"

function* watchFetchRecipes() {
  while (true) {
    // Wait for `RECIPES.REQUEST` actions and extract the `request` payload
    const {request} = yield take(actions.RECIPES.REQUEST)

    try {
      // Fetch the recipes
      const recipes = yield call(api.fetchRecipes(request))

      // Send a new action to notify the UI
      yield put(actions.fetchRecipes.success(recipes))
    } catch (e) {
      // Notify the UI that something went wrong
      yield put(actions.fetchRecipes.error(e))
    }
  }
}

function* rootSaga() {
  yield [
    fork(watchFetchRecipes)
  ]
}

And that's it! Whenever a component will send a RECIPES.REQUEST action, the saga will hook up and handle the async workflow.

dispatch(recipes.request(req))

What's awesome with redux-saga is that you can easily chain async effects and dispatch actions during the workflow.

like image 158
Florent Avatar answered Sep 19 '22 11:09

Florent


Based on your description, the only time you actually update your UI is right at the end of all these asynchronous operations (B1).

If you don't use the results from the preceding async calls to change your application state / update your UI, what is the benefit of having these fine-grained actions?

Of course there are things like "loading / request started" and "finished loading / request stopped", but it seems to me, that in your case, you could just do the chained async calls outside of redux (in some kind of API-layer) and only use one action. This action dispatches a "REQUEST_STARTED", then calls the API-layer, which does the DB-calls and elasticsearch request etc., and then dispatches either "REQUEST_SUCCESS" or "REQUEST_FAILURE", based on the result of the promise, which will give you the data you need to update your UI.

This way, the state in redux only concerns itself with ONE side-effect, instead of the implementation details of your chained calls. Also, your action gets a lot simpler, because it just handles the results of one async call.

like image 31
mzupan Avatar answered Sep 21 '22 11:09

mzupan