bmealhouse / next-redux-saga

redux-saga HOC for Next.js
MIT License
182 stars 33 forks source link

After dispatching an action the saga returns prematurely #71

Closed myasul closed 4 years ago

myasul commented 4 years ago

I am running this code in the server-side of our project. What it does is to fetch all the pizzas in a menu then waiting if the saga for the action I first dispatched succeeded. It will then fetch the default selected pizza after. But it seems that it is returning prematurely and not waiting the take action to finish (I also tried race but got the same result.

Am I missing something? I tried using takeEvery and takeLatest in the rootSaga calling this saga but same outcome.

export function* loadInternalPizzaState (pizzaSlug: string, pizzaApi?: PizzaApi) {
    yield put(new FetchAllPizzaAction().create())

    const resultAction = yield take([
        FetchAllPizzaSuccessAction.name, 
        FetchAllPizzaFailedAction.name
    ])

    // Never runs the code up to this point    

    const api = pizzaApi || new PizzaApi()
    let pizza: Pizza
    if (pizzaSlug) {
        pizza = yield call([api, api.getPizzaFromSlug], pizzaSlug)
    } else {
        pizza = yield call([api, api.getDefaultPizza])
    }

    if (pizza instanceof Error) throw new Error(pizza.message)

    yield put(new SelectPizzaAction(pizza).create())
    yield take(FetchAllSidesFromPizzaSuccessAction.name)
}
bbortt commented 4 years ago

you cannot await sagas: redux-saga is explicitly for handling asynchronous side-effects. It is therefore impossible for this library to know which of your sagas it should await and which not (as for example the root-saga runs infinite). that's why anything after the dispatch(END) will not run on serverside. does this clarify the behavior?

myasul commented 4 years ago

Thank for your quick response!

I'm sorry for the newbie questions, I'm just quite confused. When does the dispatch(END) being called? The above code works perfectly on the client-side. What it does is it dispatches the FetchAllPizzaAction then the yield take awaits for the success action from the FetchAllPizzaAction to be dispatched.

take Creates an Effect description that instructs the middleware to wait for a specified action on the Store. The Generator is suspended until an action that matches pattern is dispatched.

But from what I read here, you can only dispatch an action and be handled by a single saga. Multiple sagas are not supported (which is what I'm trying to do with the code above.). Just wanted to clarify if that is the case? And a brief explanation of why?

bbortt commented 4 years ago

No problem at all. I'm trying to help as good as I can. So, this library wraps your application using a so called HOC (higher order component). This allows us to intercept getInitialProps and then stop redux-saga on server side only. If we wouldn't, the redux-saga would run infinite and end in a stack overflow at any point. That's why it works on client side.

Our code is located in one single file; the index.js:

// Stop saga on the server
if (isServer) {
  store.dispatch(END)
  await store.sagaTask.toPromise()
}

And that's also the problem right there. If you're running anything asynchronously it comes "to late", meaning END was already dispatched and the channel (the underlying technology of redux-saga) has been closed.

Then finally, if you want to take multiple actions, you're on the right way ;) From the docs:

The most common use case is an array of strings though, so that action.type is matched against all items in the array (e.g. take([INCREMENT, DECREMENT]) and that would match either actions of type INCREMENT or DECREMENT).

myasul commented 4 years ago

Great explanation! It all makes sense to me now. I got it working as well. Thanks a lot again for the time!

bbortt commented 4 years ago

No problem at all. Glad I could help!

einaralex commented 4 years ago

@myasul Could you explain how you managed to solve this? I'm having the exact same problem.