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

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.

Related

What is the difference between publish / subscribe and method ? where/when we use method and publish / subscribe?

Today I try to work on meteor and getting an issue to get data from the server page..
I try to search it on google and meteor side but there is two way to get data publish / subscribe and Method
Have a below code, I didn't know how to write it on meteor server-side and get that data in client-side
function (x)
{
var y =2
var z = y*x
return z;
}
Now I want to call this method in client side
With publish/subscribe you keep track on datas collected.
https://docs.meteor.com/api/pubsub.html
According to the documentation:
Methods are remote functions that Meteor clients can invoke with
Meteor.call.
So, to answer the last question: to call your function from client side, you have to use "Meteor.call('yourMethodName', optionalCallBacks);"
EDIT:
Ok, as suggested in the comments, here an example, with your function.
In the server side, lets say on a file called "methods.js" or somethings:
import { Meteor } from 'meteor/meteor';
Meteor.methods({
myMethod(x) {
const y = 2;
const z = y * x;
return z;
}
});
then in client side, you could call this mehod, like:
Meteor.call('myMethod', x, (error, result) => {
// OPTIONAL CALLBACK RESULT
console.log(error, result);
});
Arguments here, are respectivelly, name of the method, variable named x, callback.
What is the difference between publish / subscribe and method ? where/when we use method and publish / subscribe?
So if you want a logic you don't need pub/sub.
Methods are for logic pub/sub for data handling.
Important note:
If you need to work with data from meteor's collection on the method you need to take into account this if method is executed on the server side it has access to all data (collections).
If the method is executed on the client side it has access only to published data.
On the other hand, according to your example, you don't need any data, so I'll skip it.
I strongly suggest using validated methods:
https://github.com/meteor/validated-method
Now let's go to examples
Imagine you have a method
export const calculate = new ValidatedMethod({
name: 'logic.calculate', // methods are usually named like this
validate: new SimpleSchema({ // use SimpleSchema to autovalidate parameters
x: {
type: Number
}
}).validator(),
run({ x }) {
const y = 2;
return y*x;
}
});
Things to note:
1. The file should be imported on the server somewhere.
2. You need to use validation
Now call it on the client
Meteor.call('logic.calculate', { x }, (error, result) => {
if (error) {
do something
}
console.log(result);
});
Also, you can import the method directly and call it like this:
import { calculate } from '../../api/logic/methods';// use real method path here
calculate.call({ x }, (error, result) => {
if (error) {
do something
}
console.log(result);
});
Note that for validated methods argument is an object
In meteor, we can create a meteor method and can pass data to client side by Meteor.call('methodName', 'param') . But in case of async operation we need to use future. Take a look on below example :
Future = Npm.require('fibers/future');
Meteor.methods({
foo: function() {
const future = new Future();
someAsyncCall(foo, function bar(error, result) {
if (error) future.throw(error);
future.return(result);
});
// Execution is paused until callback arrives
const ret = future.wait(); // Wait on future not Future
return ret;
}
});

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.

Process queue with redux-saga

I am trying to implement a queue handler for managing notifications with a redux-saga generator.
Basically, I need to show notifications sequentually as they enter the queue.
For this, I have a queue array in the redux store, an action QUQUE_NOTIFICATION action to add to queue and SHOW_NOTIFICATION to remove a notification for queue.
My current saga implementation is that simple :
export function* watchQueue() {
while (true) {
const state = yield select()
const queue = state.queue
if (queue.length > 0) {
yield put({ action: 'SHOW_NOTIFICATION', queue[0])
}
yield call(delay, 5000);
}
}
}
The problem with current implementation is that when a queue is empty a QUQUE_NOTIFICATION is dispatched generator can be waiting for the delay to finish.However, I want to show the first notification as soon as it enters the queue. Any ideas?
I've had the same idea for showing up notification (queueing them) however saga provides already implemented solution in terms of channels.
I have:
export function * notificationSaga () {
const requestChan = yield actionChannel(Notification.request)
while (true) {
const { payload } = yield take(requestChan)
yield call(showNotification, payload)
}
}
which I believe is elegant solution to your problem.
showNotification is another function which actually shows notifications and waits a bit before taking it down.

Store browserHistory using history.js in react redux architecture with SSR

How can one persist the full router history of a user visiting an SSR react-redux app? I have tried modifying the react-redux-router package's reducer.js file as such...but when the user loads via SSR, the history array is reset.
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '##router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null,
locationHistory: []
}
/**
* This reducer will update the state with the most recent location history
* has transitioned to. This may not be in sync with the router, particularly
* if you have asynchronously-loaded routes, so reading from and relying on
* this state is discouraged.
*/
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state,
locationBeforeTransitions: payload,
locationHistory: state.locationHistory.concat([payload]) }
}
return state
}
ref: https://github.com/reactjs/react-router-redux/blob/master/src/reducer.js
However, I think this is supposed to be achieved in a middleware.
Irregardless, this (storing the entire previous session history) seems like a common enough use case that perhaps someone has already formulated a best practice.??
Perhaps even this full history is accessible via the historyjs object in react-router w/o react-router-redux.
I'm looking for answers to how to fulfill storing the full history of a user's session in the redux state and post it to my api server when the user closes the browser or navigates away from the site. (if this is not possible, i could just post it upon every navigation.) Then I would like to show this history in a 'recently viewed' list of pages on the users' home pages.
First of all, you don't have to meddle with the internals of react-redux-router.
As you can see in the code you presented, react-redux-router exports a LOCATION_CHANGE action.
You can use this action in a reducer of your own. Here's an example:
// locationHistoryReducer.js
import { LOCATION_CHANGE } from 'react-router-redux';
export default function locationHistory(state = [], action) {
if (action.type === LOCATION_CHANGE) {
return state.concat([action.payload]);
}
return state;
}
However, this may be unnecessary. Your assumption that this can be be achieved with middleware is correct. Here's an example of a middleware layer:
const historySaver = store => next => action => {
if (action.type === LOCATION_CHANGE) {
// Do whatever you wish with action.payload
// Send it an HTTP request to the server, save it in a cookie, localStorage, etc.
}
return next(action)
}
And here's how to apply that layer in the store:
let store = createStore(
combineReducers(reducers),
applyMiddleware(
historySaver
)
)
Now, how you save and load data is entirely up to you (and has nothing to do with react-router and the browser's history).
In the official docs, they recommend injecting the initial state on the server side using a window.__PRELOADED_STATE__ variable.

Handling Side-Effects in Async (thunk'd) Actions

I have an async actionCreator which handle my app's authentication flow:
function createAuthenticationResponse(err, grant) {
return {
type: AUTHENTICATION_RESPONSE,
payload: err || grant,
error: Boolean(err)
}
}
function authenticate() {
// return a thunk.
return dispatch => {
// Notify the system that we are authenticating.
dispatch({ type: AUTHENTICATE });
// Trigger the auth flow.
myAuthModule.authorize((err, grant) => {
// Trigger a state-change on the outcome.
dispatch(createAuthenticationResponse(err, grant));
// Q: How do I handle this side-effect?
if (!err) {
dispatch(extractUserInfo(grant));
}
});
};
}
My actionCreator contains business logic to extract the user info from the grant if the user was succesfully authenticated; should this logic live in my action creator? If not, where should I place it, inside my reducer?
In other architectures I would bind a command to trigger on AUTHENTICATION_RESPONSE; but this doesn't feel like a middleware job?
I think what you're suggesting is totally sensible.
You can use Redux Thunk both for control flow and side effects.
You should never put side effects into the reducers.

Resources