redux-saga: tracking multiple async tasks - redux

I am using sagas to track multiple async tasks, but there is one problem I haven't been able to completely solve:
function* performTask1() {
// Some logic here to takeLatest for the relevant component
// check if component id matches?
// Only perform API call with the latest
const { result } = yield takeLatest('doAsync2')
}
function* performTask2() {
const { result } = yield call(api, args)
// do something with results (not relevant)
}
function* watchAsyncTasks() {
yield takeEvery('doAsync2', performTask2)
yield takeEvery('doAsync1', performTask1)
}
componentA dispatches doAsync1
componentB dispatches doAsync1
component C dispatches doAsync2 (for good measure)
componentA dispatches doAsync1
componentB dispatches doAsync1
How can I use sagas to ensure that only sagas 3, 4, and 5 complete their API call?

function* generator(){
yield call(api,params);
yield call(api2, params2);
}
const gen = generator;
gen.next() // done: false/true
gen.next() // done: false/true

Related

RTK createListenerMiddleware saga channels

I'm using RTK createListenerMiddleware in my project, I was wondering if there's a recipe for Redux-saga like channels pattern.
I'm referring to this Saga pattern:
import { channel } from 'redux-saga'
import { take, fork, ... } from 'redux-saga/effects'
function* watchRequests() {
// create a channel to queue incoming requests
const chan = yield call(channel)
// create 3 worker 'threads'
for (var i = 0; i < 3; i++) {
yield fork(handleRequest, chan)
}
while (true) {
const {payload} = yield take('REQUEST')
yield put(chan, payload)
}
}
function* handleRequest(chan) {
while (true) {
const payload = yield take(chan)
// process the request
}
}
https://redux-saga.js.org/docs/advanced/Channels/#using-channels-to-communicate-between-sagas
I'm a Redux maintainer and I created the listener middleware.
We designed the listener middleware to do almost all the things that you could do with sagas: listen for actions, pause, cancelation, and even forking off "child tasks".
However, we intentionally did not reimplement everything that sagas could do, to save on complexity and bundle size. Channels are a saga capability that we chose to not mimic in the listener middleware.
It's possible you could do something sorta-similar yourself in some way, but there's definitely nothing built into the listener middleware API that is similar to channels.

redux saga before action

I have whole application depending on config data that are loaded with request from server, how can I create blocking "before" action on every action using redux saga, now my globalSaga looks like this. The help would be really appreciated
function * rootSaga () {
yield takeLatest(LOAD_ONBOARDING.REQUEST,loadOnboardingSaga)
const res = yield put.resolve(loadOnboarding())
yield console.log(res)
yield all([
fork(globalSaga),
fork(spaceSaga),
fork(profileSaga),
fork(userSaga),
fork(pagesSaga)
])
}
takeLatest is the same as forking with automatic cancellation of each subsequent fork. Check this for a descriptive example.
So in your case since loadOnboardingSaga should continuously block next calls the cheapest solution would be to put it all under loadOnboardinSaga since there is a direct succession like this:
function* initRestSaga() {
yield all([
fork(globalSaga),
fork(spaceSaga),
fork(profileSaga),
fork(userSaga),
fork(pagesSaga)
])
}
function* loadOnboardingSaga() {
//...
const res = yield put.resolve(loadOnboarding())
yield console.log(res)
yield call(initRestSaga)
}
function* rootSaga() {
yield takeLatest(LOAD_ONBOARDING.REQUEST, loadOnboardingSaga)
}
Otherwise you have to manually fork and cancel each and every side effect in between takeLatest and last fork.

How to make sure that one Saga completes before the other?

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?
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.

Access this.props.dispatch and other action creators from component

I want to use both this.props.dispatch and my own defined actions creators to dispatch actions from my react view component.
However, I can not access this.props.dispatch if I pass in mapDispatchToProps to connect(). So, this is the workaround that I came up with:
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updateRackGroup,
cloneRackGroup,
onSelectRack,
onCloneRack,
valRackChange,
valRackAdd,
onRackAction,
valRackUpdate,
dispatch
}, dispatch);
}
function mapStateToProps({
tempRackStates,
rackGrpsAdded,
invalidFields
}) {
return {
tempRackStates,
rackGrpsAdded,
invalidFields
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RackgroupGenerator);
I am not happy with it, as "it wraps dispatch within dispatch".
So is there an elegant way to gain access to the raw dispatch method under this scenario?
You don't need to pass dispatch do bindActionCreators. Just merge it to the bound action creators
function mapDispatchToProps(dispatch) {
const actionCreators = bindActionCreators({
actionCreator1,
actionCreator2,
}, dispatch);
return { ...actionCreators, dispatch };
}
// or without the additional assignment
return {
dispatch,
...bindActionCreators({ actionCreator1, actionCreator2 }, dispatch)
};
BTW I didn't test it for sure, but I also think you shouldn't "wrap dispatch with dispatch" because I believe it can lead to strange behavior such as unwillingly dispatching actions more than once.

Why separate actions + reducers In Redux?

I've seen the argument for separating actions and reducers because they have a many-to-many relationship.
I don't think that actually applies in Redux though. Because there's only 1 datastore, actions to reducers should be 1-to-many.
Typically reducers apply to a specific change for a specific datastore.
MY_ACTION = "MY_ACTION"
function reducer(state, action) {
switch(action.type) {
case MY_ACTION: // stuff with my action to create new state
default: return state
}
}
We can combine multiple reducers with combineReducers so why not define the handler for an action with the action itself.
For instance
class Action {
constructor(type) {
this.type = type
this.handlers = []
}
add_handler(handler) {
this.handlers += handler
}
get_reducer() {
reducer = combineReducers(this.handlers)
return (state, action) => {
if(action.type == this.type) {
return reducer(state, action)
}
return state
}
}
}
With the "ducks" pattern, we end up putting the main reducers in the same module as the action declaration.
Is there any reason to keep reducers + actions separate with redux?
The main reason for separating the action creators from the reducer function is that the reducer function must be a pure function. If you wanted to do something in an action creator, like an asynchronous API call for instance, then you could not put this in the reducer. There's a great explanation on this here.

Resources