Search code examples
reactjsreduxredux-saga

React Redux with SAGA - Create a queue of the same action


I have an app that retrives data with axios 50 items per time and paginates them. Initially I make a double call, the first prepares the current page (25 items), the second the next page (25 items).

Then every time the user clicks next page, it makes a call for other 50 items and so on.

So the app retrives "under the hood" the data and prepares the next page for a better UX.

The my question is this, how the I create a queue of calls with SAGA?

If the user clicks multiple times next page I have a "congestion" of calls that maybe (already tested) the 3th finishes after the 4th then the data are not in order.

What I would like to have is something like:

1 call => end =>
2 call => end =>
3 call => end =>
4 call => end =>

and so on...

The function I use is setSpotifyData()

Thats my code:

// first call just for the token and when the token expires

function* getSpotifyData(obj) {
  try {
    yield put({
      type: "GET_SPOTIFY_USER_DATA_SUCCESS",
      payload: obj.payload,
    });
  } catch (error) {
    yield put({ type: "GET_SPOTIFY_USER_DATA_ERROR", payload: error });
  }
}

// the function I would like to "queue":

function* setSpotifyData(obj) {
  const url = "http://localhost:8888/api/spotifyuserdata";

  // route the needs auth
  const config = {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${obj.payload.token}`,
    },
    // becomes query in the route
    params: obj.payload,
    url: url,
    transformResponse: [
      (data) => {
        return data;
      },
    ],
  };

  try {
    const spotifyData = yield axios(config);
    /**
     * @description Remapping to make it lighter
     */

    ...code...

    yield put({
      type: "SET_SPOTIFY_USER_DATA_SUCCESS",
      payload: newAlbumsArr,
    });
    return newAlbumsArr;
  } catch (error) {
    yield put({ type: "SET_SPOTIFY_USER_DATA_ERROR", payload: error });
  } finally {
    yield put({ type: "SET_SPOTIFY_USER_DATA_END" });
  }
}

function* watchAllSpotifyUserDataSaga() {
  yield takeEvery("GET_SPOTIFY_USER_DATA", getSpotifyData);
  yield takeEvery("SET_SPOTIFY_USER_DATA", setSpotifyData);
}

export default watchAllSpotifyUserDataSaga;

I already tried with throttle of SAGA. but doesn't actually change.

I thougth also to disable the button on loading but I would like to avid it and learn something new.

Thanks in advance


Solution

  • You are using takeEvery helper which takes all GET_SPOTIFY_USER_DATA actions and calls getSpotifyData but the thing is it allows multiple getSpotifyData instances to be called simultaneously. In other words, you may start new task when previous is not yet terminated, this is why your data may come in incorrect order.

    You can use actionChannel effect which according to the documentation makes a queue of actions and takes them one by one. Meaning we take the next action when previous is processed.

    For this in your getSpotifyData function you need to create a channel

    function* getSpotifyData(obj) {
      const usersChannel = yield actionChannel('GET_SPOTIFY_USER_DATA')
    
      while (true) {
       try {
        const {payload} = yield take(usersChannel)
    
        yield call(setSpotifyData, payload)
      } catch (error) {
        console.error('some error:', error)
      }
    }
    

    Actions from getSpotiFyData which you sent to store via put need to be moved to your worker saga which is setSpotiFyData, if you need those.

    Your saga watchAllSpotifyUserDataSaga needs to be modified as well now.

    You can rewrite it as follow:

    export default function* watchAllSpotifyUserDataSaga() {
      yield all([
        getSpotiFyData(),
        //some other sagas but I don't think you need to include setSpotifyData here
        //as it's called from the other saga
      ])
    }