Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait for an action with TAKE after END is dispatched for SSR #1117

Closed
klis87 opened this issue Aug 1, 2017 · 8 comments
Closed

Wait for an action with TAKE after END is dispatched for SSR #1117

klis87 opened this issue Aug 1, 2017 · 8 comments

Comments

@klis87
Copy link
Contributor

klis87 commented Aug 1, 2017

I have a problem with fetching all of my dependencies during SSR with sagas - I use real world example and END action.

The following works:

function* articlesSaga() {
  yield put(fetchArticles());
  const articles = yield call(getPublicArticles);
  yield put(fetchArticlesSuccess(articles.data));
}

Above, saga is not blocked on take and it will do its job before runSaga().done.then is triggered.

However, I want to switch to https://github.com/svrcekmichal/redux-axios-middleware for my api calls, so I need something like this:

function* articlesSaga() {
  yield put(fetchArticles());
  const articles = yield take.maybe(FETCH_ARTICLES_SUCCESS); // I want to block until FETCH_ARTICLES_SUCCESS is dispatched by axios middleware
}

The problem is, that take.maybe receives END signal, even if i yield it multiple times. Is there any way to do some cleanup of saga after END is dispatched, to force it to wait for a final action? Because right now, articlesSaga is terminated before FETCH_ARTICLES_SUCCESS goes to my reducer so I cannot set articles as my initial state.

@klis87
Copy link
Contributor Author

klis87 commented Aug 1, 2017

Actually I have just found a solution:

function* articlesSaga() {
  yield put.resolve(fetchArticles());  // axios middleware actions return promises, so this blocks saga until it is resolved
}

I will leave this issue opened, as I am still wondering whether it is still possible to block on take to receive an action other than END. I tried:

while(true) {
  const action = yield take.maybe('SOME_ACTION');
  if (action.type !== END.type) {
    break;
  }
}

but this just leads to an infinitive loop, so I guess you cannot receive any action other than END after END is dispatched.

@Andarist
Copy link
Member

Andarist commented Aug 1, 2017

END closes internal stdChannel so no other signals than END can be 'taken' from the moment it gets dispatched (like you have noticed).

You might want to try restructure your code (cannot give any real advice here without having deeper knowledge about your codebase) in a way that articlesSaga would became independent of the FETCH_ARTICLES_SUCCESS.

You might also consider using channels for inter saga communication. I think this would allow you to bypass the stdChannel restriction.

@klis87
Copy link
Contributor Author

klis87 commented Aug 2, 2017

Ok, thanks for confirmation. In my case articlesSaga is actually independent on FETCH_ARTICLES_SUCCESS - only a reducer cares about this action. The problem is, that articlesSaga needs to be blocked until FETCH_ARTICLES_SUCCESS (this action is sent by Axios middleware after AJAX call promise is resolved) for SSR purposes. If saga is destroyed by END before FETCH_ARTICLES_SUCCESS, I will start SSR before reducer picksFETCH_ARTICLES_SUCCESS, so my HTML won't include article list. Fortunately the trick with put.resolve works for me.

Thanks for the tip with stdChannel, custom channels for inter saga communications might come in handy as well for some cases 👍

@klis87 klis87 closed this as completed Aug 2, 2017
@Andarist
Copy link
Member

Andarist commented Aug 2, 2017

@klis87 aint sure how ur code is structured, but using 2 async sources of actions - axios middleware and sagas - might cause u some headaches like that. They both ofc work just fine together, but sometimes integration in trickier cases (like SSR) might be harder.

@klis87
Copy link
Contributor Author

klis87 commented Aug 2, 2017

@Andarist OK, I see. My main problem is, that I would like to use axios interceptors, so I will need to create axios instance for each request so each axios instance would have acess to a separate store. The problem is, how my sagas could get access to this instance? I thought about passing it as saga.run param, but I use duck structure for my stores - I have many modules with separate actions, reducers, sagas and I connect those with combineReducers and my rootSaga - but then my rootSaga would need to pass axios to all children sagas and it would get a little messy. I was thinking also about createSagaMiddleware.emitter, so I could inject axios there and it would be available as part of payload of each action, what do you think about this solution?

@Andarist
Copy link
Member

Andarist commented Aug 2, 2017

You can leverage the context feature (which is not documented unfortunately atm). You can use setContext to store ur reference and getContext to retrieve it (by using the same key) from any children saga - no matter how deep in the tree.

@klis87
Copy link
Contributor Author

klis87 commented Aug 2, 2017

ohhh great! that's exactly what I needed! Maybe I will even write a library like redux-saga-axios using this feature :) or... maybe this would be an overkill, as it is so easy to integrate redux-saga with just anything anyway :)

@Nantris
Copy link

Nantris commented Nov 6, 2018

@klis87, awesome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants