0

I'm using redux-api-middleware for api calls, and redux-sagas for side-effects. For a particular form flow, user can make 2 different changes, that needs to be handled by separate apis. And the whole saga should fail if we one of them fails. I have a separate saga that handles failure. In some cases, this breaks. When we look in the developer tools, the saga seems to run twice, jumping to the initial point before running through the steps again. I've marked this as comments. The action thats triggering the saga is only run once (verified in redux dev tools)

Here's the mock code -

Actions -

function changesAApi() {
  return (dispatch, getState) => {
    dispatch({
      [CALL_API]: {
        types: ['changes-A', 'changes-A-success','changes-A-failure'],
        endpoint: '/changesA',
        method: 'post',
      }
    })
  }
}

// can be called individually, but state in A affects this call
function changesBApi() {
  return (dispatch, getState) => {
    dispatch({
      [CALL_API]: {
        types: ['changes-B', 'changes-B-success','changes-B-failure'],
        endpoint: '/changesB',
        method: 'post',
      }
    })
  }
}

Sagas -

function* saveFormChanges(action) {
  // point (1)
  if (action.changesA) {
    yield put(changesAApi());
    // jumps to point (1) the first time
    yield take('changes-A-success');
    // point (2)
  }
  if (action.changesB) {
    yield put(changesBApi());
    // jumps to point (2)
    yield take('changes-B-success');
    // point (3)
  }
  // jumps to point (3)
  yield call(displaySuccessMessage);
}

function* errorSaga() {
  yield takeLatest(action => /failure$/.test(action.type), () => {
    alert('failure');
  });
}

Is this the right way to create this flow? Is there a better way to do this?

Madhu
  • 1,019
  • 4
  • 10
  • 24

1 Answers1

0

Honestly, I would structure this in a completely different way. To me, it seems that the problem is too many tools. The simple solution is to use one or the other. Personally, I like to use sagas for all of my API calls since they give me greater control over how my API is consumed.

The sagas documentation gives a basic example as to how sagas work. It is a really awesome tutorial to get up and running quickly, but I found sagas to be a lot less limiting once I learned about the proper way to compose sagas.

Composing sagas in this fashion enable you to handle asynchronous state changes in a much more elegant way and greatly improve code readability.

This is a quick little snippet I put together illustrating one possible solution for your issue:

// dispatch from component
const getUsers = () => dispatch({type: 'GET_USERS'})

function* watchGetUsers() {

    while(true) {
        try{
            yield take('GET_USERS')
            yield put({ type: 'REQUEST/GET_USERS' })

            const data = yield call(methodThatCallsAPI, args)
            // side effects stuff
            yield put({ type: 'SUCCESS/GET_USERS', payload: data})
            // redux reducer handles changing state
        } catch (err) {
            yield put({ type: 'FAILURE/GET_USERS', payload: err })
            // redux reducer handles changin state
            // coul also just use put to pass logic to error saga
        }

    }
}

You can also use sagas to block any concurrent requests that are made or cancel unfinished ones. This is another example of how you can make your sagas blocking:

// put sagas into an object
const apiSagas = {
    'GET_USERS': watchGetusers,
    //  other sagas
}

function* consumeApiRequestsBlocking() {
    while(true) {
        // dispatch({ type: 'API_REQUEST', payload: {type: GET_USERS}})
        const request = yield take('API_REQUEST')

        yield call(apiSagas[request.type])
        // 'API_REQUEST' cannot be dispatched until above line finishes
        // since call is blocking; fork is non-blocking

        // NOTE: you sould remove the yield take('GET_USERS') from 
        // watchgetUsers in this example
    }
}

Good Luck!

EJ Mason
  • 2,000
  • 15
  • 15