Redux saga dispatching actions from inside map inside saga-action

Viewed 303

I have API calls in the following manner:

  1. Call main api which gives me an array of objects.
  2. For each object inside array I have to call another API asynchronously.
  3. As soon as the sub API call for the object is finished, update its data in redux store which is an array (ofc) and show it.

So the scenario is a list showing items which grow in dynamic fashion.

Since I am using redux-saga, I have to dispatch the second part from inside an redux-action. I have tried the follow way:

const response = yield call(get, 'endpoint')

const configHome = response.map(function* (ele) {
    const data = yield call(get, ele.SomeURI + '?someParameter=' + ele.someObject.id)
}))

This doesn't work since map doesn't know anything about generator functions. So I tried this:

const response = yield call(get, 'endpoint')

const configHome = yield all(response.map((ele) => {

     return call(get, paramsBuilder(undefined, ele.CategoryID))

}))

But this will block my UI from showing available data untill all sub API calls are finished.

I have also tried making a separate generator function which I call from inside map and call its .next() function but the problem here again is that saga doesn't control that generator function so the call effect doesn't return any value properly.

Completely stuck on this part. Would appreciate any help.

2 Answers

Have you tried this, I have created a sample this might help

import { put, takeLatest, all, call } from 'redux-saga/effects';
function* _fetchNews(id) {
  const data = yield fetch(
    `https://jsonplaceholder.typicode.com/todos/${id}`
  ).then(function(response) {
    const data = response.json();
    return data;
  });
  console.log(id);
  yield put({ type: 'NEWS_RECEIVED', data });
  return data;
}

function* _getData() {
  const json = yield fetch('https://jsonplaceholder.typicode.com/todos').then(
    response => response.json()
  );
  return json;
}

function* fetchNews() {
  const json = yield _getData();
  const configHome = json.map(ele => _fetchNews(ele.id));
  for (var item of configHome) {
    yield item;
  }
}

function* actionWatcher() {
  yield takeLatest('GET_NEWS', fetchNews);
}

export default function* rootSaga() {
  yield all([actionWatcher()]);
}

yield all - the generator is blocked until all the effects are resolved or as soon as one is rejected

So you will need to dispatch events separately for each sub API

Let's assume you have 2 actions:

export const getMainApi =() => ({
  type: types.GET_MAIN_API,
});

export const getSubApi = endpoint => ({
  type: types.GET_SUB_API,
  endpoint,
});

Then your operations will be:

const get = endpoint => fetch(endpoint).then(response => response);

function* fetchMainApi(action) {
  try {
    const response = yield call(get, 'endpoint');
    for (let i = 0; i < response.length; i += 1) {
      // dispatch here all sub APIs
      yield put(
        getSubApi(
          response[i].SomeURI + '?someParameter=' + response[i].someObject.id,
        ),
      );
    }
  } catch (e) {
    console.log(e);
  }
}

function* fetchSubApi(action) {
  try {
    const response = yield call(get, action.endpoint);
    yield put({
      type: types.RECEIVE_SUB_API,
      response
    });
  } catch (e) {
    console.log(e);
  }
}

takeLatest(type.GET_MAIN_API, fetchMainApi)
takeEvery(types.GET_SUB_API, fetchSubApi)

So on success of receiving sub APIs you need to insert data into your state inside reducers.

This is just pseudo code.

Related