0

I'm a noob in redux-sagas, so please bear with me if I miss out something obvious!

I have a scenario wherein I'm using redux-saga to fetch an access_token from an OIDC provider and store it in the browser's localStorage. I also use Sagas to fetch some data from an API endpoint. But I've been experiencing problems with this approach because the Saga calling the external API gets invoked before the Auth Saga can resolve with an access_token.

My Auth Saga :

export function * handleFetchTokens () {
  try {
    const token = yield cps(getToken)
    localStorage.setItem('token', token)
    const isAuthenticated = !!(token)
    yield put(actions.checkAuthSuccess(isAuthenticated))
  } catch (e) {
    yield put(actions.checkAuthFailure(e))
  }
}
export default function * sagas () {
  yield fork(takeLatest, actions.CHECK_AUTH, handleFetchTokens)
}

My API Saga :

export function * handleFetchItems () {
  try {
    const response = yield call(getItems)
    yield put(actions.fetchItemsSuccess(response))
  } catch (e) {
    yield put(actions.fetchItemsFailure(e.errors))
  }
}
export default function * sagas () {
  yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}

My root Saga :

export default function * root () {
  yield fork(items.sagas)
  yield fork(authentication.sagas)
}

What should be the proper way of overcoming this problem?

Hawkes
  • 457
  • 1
  • 4
  • 16

1 Answers1

0

Personally, I'd make sure the token is received before allowing any part of my app to actually call the FETCH_ITEMS action. Assuming you don't want to introduce such logic you will have to decide what to do with FETCH_ITEMS actions before you get the token.

The easiest approach would be to just ignore them, but that also probably isn't the most feasible way to go.

So what remains is to buffer the FETCH_ITEMS actions. You can do this using actionChannel. Since you are using takeLatest you will also want to define a sliding buffer of size 1.

It could look ruffly like this:

export default function * sagas () {
  const chan = yield actionChannel(actions.FETCH_ITEMS, buffers.sliding(1))
  yield take('TOKEN_RECEIVED') // wait for action informing us token was received
  chan.close()
  yield fork(takeLatest, chan, handleFetchItems)
  yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}

More about actionChannels here https://redux-saga.js.org/docs/advanced/Channels.html


Another approach - with a bit less writing but also a bit less control - instead of buffering is to wait in the fetching saga itself:

export function * handleFetchItems () {
  while (!token) yield take('TOKEN_RECEIVED');
  ...
}

Either way, both ways rely on waiting for a TOKEN_RECEIVED action you need to dispatch once the token is received.

Martin Kadlec
  • 4,702
  • 2
  • 20
  • 33
  • Although I'm still struggling to make this work, your suggestions seem pretty good. I was wondering if I could implement something similar to what you said: 'fetching the token before any action is dispatched from the app'. This sounds a lot better! Basically, I dispatch the action of fetching the token on `react-router`s `onEnter` and due to the async nature of sagas, it gets pretty tricky to handle all other actions that require this token to be used. – Hawkes Aug 04 '17 at 08:47
  • Maybe, at the start of your application (before rendering your app/router) you could just render simple loader and request the token. Once you receive the token you can render the whole app including react router. This way there will be no view logic that could in turn call any other action requiring the token before it is available. – Martin Kadlec Aug 04 '17 at 08:56