#ngrx Action called Infinitely with Effects - ngrx

Please forgive me if this is an easy answer. I have a complicated login logic that requires a few calls before a user has a complete profile. If a step fails, it shouldn't break the app -- the user just doesn't get some supplemental information.
The flow I'm looking to achieve is this:
Call Revalidate.
Revalidate calls RevalidateSuccess as well as ProfileGet (supplemental fetch to enhance the user's state).
ProfileGetSuccess.
To save tons of code, the actions exist (it's a giant file).
The app kicks off the action: this._store.dispatch(new Revalidate())
From there, we have the following effects:
#Effect()
public Revalidate: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE),
map((action: Revalidate) => action),
// This promise sets 'this._profile.currentProfile' (an Observable)
flatMap(() => Observable.fromPromise(this._auth.revalidate())),
// Settings are retrieved as a promise
flatMap(() => Observable.fromPromise(this._settings.get())),
switchMap(settings =>
// Using map to get the current instance of `this._profile.currentProfile`
this._profile.currentProfile.map(profile => {
const onboarded = _.attempt(() => settings[SettingsKeys.Tutorials.Onboarded], false);
return new RevalidateSuccess({ profile: profile, onboarded: onboarded });
}))
);
//Since I couldn't get it working using concatMap, trying NOT to call two actions at once
#Effect()
public RevalidateSuccess: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE_SUCCESS),
mapTo(new ProfileGet)
);
#Effect()
public ProfileGet: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.PROFILE_GET),
// We need to retrieve an auth key from storage
flatMap(() => Observable.fromPromise(this._auth.getAuthorizationToken(Environment.ApiKey))),
// Now call the service that gets the addt. user data.
flatMap(key => this._profile.getCurrentProfile(`${Environment.Endpoints.Users}`, key)),
// Send it to the success action.
map(profile => {
console.log(profile);
return new ProfileGetSuccess({});
})
);
Reducer:
export function reducer(state = initialState, action: Actions): State
{
switch (action.type) {
case AuthActionTypes.REVALIDATE_SUCCESS:
console.log('REVALIDATE_SUCCESS');
return {
...state,
isAuthenticated: true,
profile: action.payload.profile,
onboarded: action.payload.onboarded
};
case AuthActionTypes.PROFILE_GET_SUCCESS:
console.log('PROFILE_GET_SUCCESS');
return { ...state, profile: action.payload.profile };
case AuthActionTypes.INVALIDATE_SUCCESS:
return { ...state, isAuthenticated: false, profile: undefined };
default:
return state;
}
}
As the title mentions, dispatching the action runs infinitely. Can anyone point me in the right direction?

The answer lies here:
this._profile.currentProfile.map needed to be this._profile.currentProfile.take(1).map. The issue wasn't the fact that all my actions were being called, but because I was running an action on an observable, I suppose it was re-running the action every time someone was touching the observable, which happened to be infinite times.
Moreso, I was able to refactor my action store so that I can get rid of my other actions to call to get the rest of the user's data, instead subscribing to this._profile.currentProfile and calling a non-effect based action, ProfileSet, when the observable's value changed. This let me remove 6 actions (since they were async calls and needed success/fail companion actions) so it was a pretty big win.

Related

Access to the latest state from redux-observable epic after api response

I have read this Accessing the state from within a redux-observable epic, but it doesn't helps me finding the answer.
I am using redux-observable in my react-redux app, I have an epic will trigger an API invoke, code as below:
const streamEpicGet = (action$: Observable<Action>, state$) => action$
.pipe(
ofType(streamActions.STREAM_ITEM_GET),
withLatestFrom(state$),
mergeMap(([action, state]) => {
return observableRequest(
{
failureObservable: error => {
const actions = []
return actions
},
settings: {
body: queryParams, // I can access to the state data here to organize the query params I need for calling the API
url: '/path/to/api',
},
successObservable: result => {
const { pushQueue } = state
// here I want to access to the latest state data after API response
}
})
}),
)
In the above code, I use withLatestFrom(state$) so I can access to the latest data when executing the mergeMap operator code, that is to say I can access to the state data here to organize the query params for API.
However, during the time after API request sends out and before it responses, there are other actions happening, which are changing the state's pushQueue, so after the API response, I want to read the latest state pushQueue in my successObservable callback.
My problem is I always get the same state data as when preparing the query param, i.e.: I cannot get the latest state data after API response in successObservable callback.
Let me know if you need more info, thanks.
const streamEpicGet = (action$: Observable<Action>, state$) => action$
.pipe(
ofType(streamActions.STREAM_ITEM_GET),
withLatestFrom(state$),
mergeMap(([action, state]) => {
return observableRequest(
{
failureObservable: error => {
const actions = []
return actions
},
settings: {
body: queryParams, // I can access to the state data here to organize the query params I need for calling the API
url: '/path/to/api',
},
successObservable: result => {
const { pushQueue } = state$.value // Use state$.value to always access to the latest state
// here I want to access to the latest state data after API response
}
})
}),
)
So the answer is to use state$.value.
I get this by reading Accessing state in redux-observable Migration document

Redux middleware not dispatching new actions

I'm creating a Redux middleware that listens for a specific action. If that action type matches what I'm looking for, I want to dispatch another action. The reason for this is because I have different components with some shared functionality, so I want to update actions to have similar types, but different payloads (term), like so:
const updateActions = store => next => action => {
console.log(action);
if (action.type === 'UNIQUE_ACTION_A') {
return next({ type: 'NEW_ACTION', term: 'new_action_a_test' });
} else if (action.type === 'UNIQUE_ACTION_B') {
return next({
type: 'NEW_ACTION',
term: 'new_action_b_test'
});
}
return next(action);
};
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk, updateActions))
);
The problem I'm having is that these actions are not dispatching. When I console.log all actions, they continue to run as normal, rather than dispatching the new actions. It's as if the call to dispatch these actions are just being ignored. What am I doing incorrectly here? Thanks!
There's a difference between next and dispatch inside middleware. dispatch sends an action to the very start of the dispatch chain, which will cause it to run through all the middleware in the pipeline. next sends it to the next middleware after this one, and eventually to the reducers. By calling next({type : "NEW_ACTION"}), you're sending it to the next middleware, and this middleware will never see "NEW_ACTION".
Also see the Redux FAQ entry on next vs dispatch in middleware.

Ngrx: meta-reducers and capturing effects

I've added into my IStore a transaction concept. It straightforwardly stands for providing a way to store into my IStore which pending operations keep pending. When they are completed, they are removed:
export interface IStore {
user: IUser;
txs: ITxRedux;
}
All my reducers are like:
* reducer name: `'OPERATION'`
* success reducer name: `'OPERATION_SUCCESS'`
* failed reducer name: `'OPERATION_FAILED'`
Some of these reducers (only those need a http request) are captured using #Effects:
#Effect({ dispatch: true })
userLogin$: Observable<Action> = this._actions$
.ofType('USER_LOGIN')
.switchMap((action: Action) =>
{
....
});
Currently, my effects have this pattern:
return make_http_call
.map(_ => ({type: 'OPERATION_SUCCESS'}, payload: {...}))
.catch(_ => ({type: 'OPERATION_FAILED'}, payload: {...}));
So, I'd like to get a way by adding or removing a "transaction" into my IStore.txs each time an effect is called or completed. When I say "add a transaction into my IStore.txs" I mean to call transaction reducers:
public static ADD_TX = `ADD_TX`;
private static addTx(txsRdx: ITxRedux, type, payload: ITx) {
const tx = payload;
return {
ids: [ ...txsRdx.ids, tx.id ],
entities: Object.assign({}, txsRdx.entities, {[tx.id]: tx}),
};
}
public static REMOVE_TX = `REMOVE_TX`;
private static removeTx(txsRdx: ITxRedux, type, payload) {
const tx: ITx = payload;
var entitiesTmp = {...txsRdx.entities};
delete entitiesTmp[tx.id];
return {
ids: txsRdx.ids.filter(id => tx.id != id),
entities: entitiesTmp
};
}
I've listen to talk a bit about meta-reducers, but I don't quite whether they are going to be able to get my goal.
Is there any way to get it using a elegant way?
Late reply, but you might find this post useful. The classic example (taken mostly from that post) is to log each action/state change by means of a logging meta-reducer:
export function logging(reducer) {
return function loggingReducer(state, action) {
console.group(action.type);
// invoke following, "wrapped" reducer in the chain
const nextState = reducer(state, action);
console.log(`%c prev state`, `color: #9E9E9E`, state);
console.log(`%c action`, `color: #03A9F4`, action);
console.log(`%c next state`, `color: #4CAF50`, nextState);
console.groupEnd();
// return wrapped reducer output
return nextState;
};
}
In main app module, you compose the new logging reducer factory with the usual combineReducers reducer factory:
const appReducer = compose(logging, combineReducers)(reducers);
//...
StoreModule.provideStore(appReducer),
Just watchout for setting up StoreModule and global app reducer, as that syntax has changed in recent ngrx versions since that blog post.
On a side note, if you're looking for some inspiration on implementing a meta-reducer to catch and invoke remote API calls, you might want to have a look at an equivalent, already-made middleware for Redux, as redux-api-middleware. HTH

Redux will execute all subscription callbacks every time an action is dispatched?

Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.

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