Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Action/Saga in another Saga

I have a Saga where I need to do 3 asynchronous requests, then use the responses from the 3 requests in a follow-up request. Here's some psuedo-code to explain:

function* useOtherActionsAndSagas(action) {
  try {
    const [response1, response2, response3] = yield [
      request1,
      request2, 
      request3
    ];

    const orderData = {
      ...response1,
      ...response2,
      ...response3,
    };

    const response4 = yield request4;

    yield put({ type: 'SUCCESS', data: response4 });
  } catch (e) {
    // handle error
  }

The 3 requests request1, request2 and request3 correspond to 3 separate Sagas. For example, for request1 there's a Saga along the lines of:

export function* request1(action) {
  try {
    const result = yield api.get(`/api/endpoint`);
    yield put({...action, type: ACTION1_SUCCESS, data: result});
  } catch (e) {
    yield put({...action, type: ACTION1_FAIL, errors: e});
  }
}

function* watchAction1() {
  yield* takeLatest(ACTION1, request1);
}

export default function* () {
  yield [fork(watchAction1)];
}

where api.get is a wrapper for Axios.get().

This watcher in that Saga is connected to a corresponding action/reducer.

export const ACTION1 = "actions/ACTION1";
export const ACTION1_SUCCESS = "actions/ACTION1_SUCCESS";
export const ACTION1_FAIL = "actions/ACTION1_FAIL";

const initialState = {
  // Initial state
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case ACTION1:
      // return state
    case ACTION1_SUCCESS:
      // return state
    case ACTION1_FAIL:
      // return state
    };
    default:
      // return state;
  }
}

export function request1(data) { return {type: ACTION1, data}; }

To keep my code DRY I was hoping to take advantage of the existing action and saga in the parent saga. To do this I tried:

const [response1, response2, response3] = yield [
  put({type: ACTION1, data: data1}),
  put({type: ACTION2, data: data2}),
  put({type: ACTION3, data: data3})
];

This correctly initiates each action and their corresponding sagas. However, the response from the requests are not available in the assigned variables. That is, response1, response2 and response3 are references to their actions {type: "actions/ACTION1", data: data1} and not a Promise.

I know it would be possible to duplicate the Axios requests in this parent Saga but I'd lose the bonus of having the success/fail actions for the individual requests.

Is it possible to use a setup like this? If so, how can the responses from the asynchronous requests be retrieved for use in a follow-up request?

If not, what is the correct method for accomplishing this?

Update

I can use the workers from the other Sagas within the parent saga, like this:

import request1 from request1Saga;

const [response1, response2, response3] = yield [
  call(request1, data1),
  call(request2, data2),
  call(request3, data3),
];

where request1, request2 and request3 are the worker functions from other Sagas. That gives the benefit of the ACTION1_SUCCESS and ACTION1_FAIL actions from those Sagas being used.

like image 414
Brett DeWoody Avatar asked Jun 12 '17 11:06

Brett DeWoody


People also ask

How do I call Saga from another Saga?

Calling a saga from another saga is as simply as doing a call effect.

Can we dispatch action from Saga?

Since you're using takeEvery , no, there's no way to dispatch a GET_USER action from within your saga without triggering an infinite loop.

Is Redux Saga deprecated?

Such a powerful & elegant tool as Redux-Saga, a Redux side effect manager, is said to be deprecated, and no longer being maintained, starting from Jan 27, 2021.

How do I dispatch actions in Saga file?

Create a plain JavaScript Object to instruct the middleware that we need to dispatch some action, and let the middleware perform the real dispatch. This way we can test the Generator's dispatch in the same way: by inspecting the yielded Effect and making sure it contains the correct instructions.


1 Answers

All you need is combine all combinator with call effect (docs for composing sagas and running tasks in parallel):

const [response1, response2, response3] = yield all([
  call(request1),
  call(request2), 
  call(request3)
]);

This will execute sagas in parallel and return results from each of them. It works as Promise.all.

The sagas above (request1 to request3) need to return some data at the end of saga:

export function* request1(action) {
  try {
    const result = yield call(url => api.get(url), `/api/endpoint`);
    yield put({...action, type: ACTION1_SUCCESS, data: result});
    // This will be assigned to result1
    return result
  } catch (e) {
    yield put({...action, type: ACTION1_FAIL, errors: e});
  }
}

Note: You don't need to fork takeEvery, because it is already "forked":

// Example of root saga:
export default function* () {
  yield takeLatest(ACTION1, request1);
  yield takeLatest(ACTION2, request2);
  // ...
}
like image 58
Tomáš Ehrlich Avatar answered Sep 22 '22 16:09

Tomáš Ehrlich