erp
erp

Reputation: 3014

Call two different action types from one redux-saga

I've been able to get by with basic sagas implementation for now but my app is getting a little more complex. I chose sagas for the asynchronous capabilities but seem to have misunderstood how things work.

I have a global search input within my application that needs to make two different api calls (different data objects), but the search input also has it's own loading states based on the search/ status of api calls. Based on this information this is the flow of the application:

  1. Search happens (dispatches the action GLOBAL_SEARCH_REQUEST)
  2. The saga watcher for GLOBAL_SEARCH_REQUEST kicks off (sets loading to true for the input)
  3. In that saga - make a call to get all users / subscriptions that match the search query
  4. On success, set loading for the input to false
  5. On failure, set error

the global search request saga

function* globalSearchRequestSaga(action) {
  const { query } = action
  console.log(`searching subscriptions and users for : ${query}`)
  try {
    yield put(fetchUsersRequest(query))
    // call for the subscriptions (leaving it out for simplicity in this example)
    yield put(globalSearchSuccess(query))
  } catch (error) {
    console.log(`error: ${error}`)
    yield put(globalSearchFailure(error.message))
  }
}

where the fetch users saga looks like

export function* fetchUsersRequestSaga(action) {
  const { query } = action
  const path = `${root}/users`
  try {
    const users = yield axios.get(path, { crossDomain: true })
    yield put(fetchUsersSuccess(query, users.data))
  } catch (error) {
    console.log(`error : ${error}`)
    yield put(fetchUsersFailure(query, error.message))
  }
}

(very basic)

If I do things this way, there is an issue where the the GLOBAL_SEARCH_SUCCESS action is executed before the completion of the request for users ( and I imagine the same thing if I added in subscriptions api call as well). One solution I found is if I change the line

yield put(fetchUsersRequest(query))

to

yield call(fetchUsersRequestSaga, fetchUsersRequest(query))

where fetchUsersRequestSaga is the saga from above, and fetchUsersRequest(query) is the action creator for fetching users. This causes the asnyc functionality to work, and GLOBAL_SEARCH_SUCCESS waits for the return of the users (correct behavior).

The only issue with this is that the FETCH_USERS_REQUEST action is no longer logged to the store.

I am wondering if there is a way to either get this to properly log to the store, or return to my previous implementation with proper blocking on the put(fetchUsersRequest(query))

Upvotes: 3

Views: 7180

Answers (2)

HMR
HMR

Reputation: 39270

It's been a while since I worked with sagas but here is some code that will give you a general idea how to wait for a dispatched action.

The way it works is that when you fetch and want to wait for it to fail or succeed you give the fetch action an id, then you can pass that to the waitFor function while simultaneously dispatch the action.

If you don't want or need to wait for it then you can just dispatch the action without an id and it'll still work:

const addId = (id => fn => (...args) => ({
  ...fn(...args),
  id: id++,
}))(0);
const withId = ({ id }, action) => ({ action, id });

function* waitFor(id) {
  const action = yield take('*');
  if (action.id === id) {
    return action;
  }
  return waitFor(id);
}

function* globalSearchRequestSaga(action) {
  const { query } = action;
  console.log(
    `searching subscriptions and users for : ${query}`
  );
  try {
    //add id to action (id is unique)
    const action = addId(fetchUsersRequest, query);
    //dispatch the action and then wait for resulting action
    //  with the same id
    yield put(action);
    const result = yield waitFor(action.id);
    // call for the subscriptions (leaving it out for simplicity in this example)
    yield put(globalSearchSuccess(query));
  } catch (error) {
    console.log(`error: ${error}`);
    yield put(globalSearchFailure(error.message));
  }
}

export function* fetchUsersRequestSaga(action) {
  const { query } = action;
  const path = `${root}/users`;
  try {
    const users = yield axios.get(path, {
      crossDomain: true,
    });
    yield put(//add original id to success action
      withId(action, fetchUsersSuccess(query, users.data))
    );
  } catch (error) {
    console.log(`error : ${error}`);
    yield put(
      withId(//add original id to fail action
        action,
        fetchUsersFailure(query, error.message)
      )
    );
  }
}

Upvotes: 0

kind user
kind user

Reputation: 41893

The put function is a non-blocking action. It won't wait till the promise/api request resolves.

I would suggest you to just call sagas directly instead of dispatching actions.

try {
   yield call(fetchUsersRequestSaga, query);
   yield call(globalSearchSaga, query); // or whatever its called
}

call is a blocking action. It will wait until the request finishes, so both if your calls will execute in proper order.

Upvotes: 3

Related Questions