Brett DeWoody
Brett DeWoody

Reputation: 62773

Dependent Asynchronous Calls in Redux Saga

I'm learning Redux Saga and for the most part it makes sense. However, I've run into a scenario where it seems like there's a better implementation.

I need to submit an order to a 3rd party website via a POST request. In order to submit the order I first need to do a GET request to a different endpoint to retrieve some info required for the POST request. That is, the POST request is dependent on the GET request successfully returning.

My current working solution looks something like this:

function* create(action) {
  try {
    const code = yield axios.get('/url/to/code?id=XXXXX'`);
    const order = {...some_object, code: code.data}
    const result = yield axios.post('/url/to/order', order);
    yield put({...action, type: CREATE_SUCCESS, data: result.data});
  } catch (e) {
      const errors = e.response.data;
      yield put({...action, type: CREATE_FAIL, errors: errors});
    }
  }

Is there a recommended way to handle this in Redux Saga?

One idea I had was to move each request into its own action, then the GET action would dispatch the POST action. Looking for input on how to do this the 'Saga' way.

Upvotes: 0

Views: 1607

Answers (1)

Dawid Karabin
Dawid Karabin

Reputation: 5293

I'm not sure if such official best practices exist but I will try show what could be improved in your code based on official documentation.

Let's read first lines from redux-saga readme:

redux-saga is a library that aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better.

Okay, you do asynchronous data fetching here. We will talk about it.

One of the best things in redux-saga is how it makes testing easy for you. Right now it will be hard to test your saga because you call axios() directly. You will have to mock/spy axios in some way or setup a fake server etc.

This is the reason why you should wrap axios with call method from redux-saga, like

import { call, put } from 'redux-saga/effects';

function* create(action) {
  try {
    const code = yield call(axios.get, '/url/to/code?id=XXXXX');
    const order = {...some_object, code: code.data}
    const result = yield call(axios.post, '/url/to/order', order);
    yield put({...action, type: CREATE_SUCCESS, data: result.data});
  } catch (e) {
    const errors = e.response.data;
    yield put({...action, type: CREATE_FAIL, errors: errors});
  }
}

Now testing code above should be very straight forward.

This is very well described in Basic concepts -> Declarative Effects in redux-saga documentation. Let's take an example from docs:

For sure working but not so correct way:

function* fetchProducts() {
  const products = yield Api.fetch('/products')
  // ...
}

The correct way, much easier testing:

function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // ...
}

It's also very exaplained, highly recommend reading the whole linked article above.

The difference from the preceding example is that now we're not executing the fetch call immediately, instead, call creates a description of the effect.

Let's back to the second part of your question:

One idea I had was to move each request into its own action, then the GET action would dispatch the POST action. Looking for input on how to do this the 'Saga' way.

It's hard to say whether splitting this as a separate saga, dispatching another action just to make a request makes sense or not.

As always, everything depends on your application needs and context and probably the best way to do something better is to answer questions like: Do you make the same request somewhere else? Do you have to share logic involved in that request between different part of your application? Do you need cancellation or retry mechanism for your request?

Upvotes: 4

Related Questions