Issue with clearing state from an ngrx/redux store when the user logs out - ngrx

My application uses ngrx/rxjs. I rely on an ngrx effect in order to signout and clear state from the store.
Unfortunately, because one of my components subscribes to the store through a selector (see below: getLatestMessagesByCounterParty) and because the state is cleared before this component is destroyed, I get the following error:
ERROR TypeError: Cannot read property 'id' of null
at getCurrentUserAccountId
... indicating that the currentUserAccount is null, which is quite logical since I have just cleared the state from the store.
Here is the signout$ effect:
#Effect()
signout$: Observable<Action> = this.actions$
.ofType(authenticated.ActionTypes.SIGNOUT)
.switchMap(() =>
this.sessionSignoutService.signout()
.do(() => {
localStorage.removeItem('authenticated');
localStorage.removeItem('sessionToken');
})
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown here...
go(['/signin'])//Never reached...
]));
Here is the component that subscribes to the logged-in state:
ngOnInit() {
this.store.select(fromRoot.getLatestMessagesByCounterParty)
.subscribe(latestMessages => this.latestMessages = this.messageService.sortMessagesByDate(latestMessages, this.numberOfConversations));
}
And the relevant selectors:
...
const getCurrentUserAccountId = (state: State) => state.userAccount.currentUserAccount.id;
const getMessagesState = (state: State) => state.message.messages;
...
export const getLatestMessagesByCounterParty = createSelector(getCurrentUserAccountId, getMessagesState, fromMessage.latestMessagesByCounterParty);
I am looking for best practices on where, when and how to clear state from the store. Ideally I would like to do that at the last possible time, when the subscribing components have been destroyed.
Can someone please advise?
edit: Let me further refine my comment. My code above should have read as follows.
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown right after this action because selector cannot find id variable on state
go(['/signin'])//Never reached...
]));

As #cgatian said, you might use a filter. But here's what would happen behind the scene with that code :
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown here...
go(['/signin'])//Never reached...
]));
You'd first dispatch an action ClearMessagesAction(null).
Then that action will be handled by your reducer.
___A new state will be produced
___Your selectors will be triggered right after
___An error will occur because you end up with an inconsistent store (as you expect that the other action ClearUserAccountAction(null) was dispatched as the same time and before the selectors kick in)
What you should do to avoid state inconsistency, is either :
- Create one action that you handle in both reducers. This way, your reducers will both be modified and only then, selectors will kick in
- Use a library that allows you to dispatch multiples actions as one (like redux-batched-actions). This way you could write something like that :
batchActions([
new ClearMessagesAction(null), --> selectors not triggered yet
new ClearUserAccountAction(null) --> selectors not triggered yet
]); --> selectors triggered now

Related

How to have a Subject as a payload in an immutable Ngrx action?

Use case: dispatch an action with a cold observable in the payload.
When an effect catches the action, it subscribes (through mergeMap, switchMap, whatever...) to this observable and send back another action. Classic Ngrx process.
export class ServicesStore {
dispatchObservable(operation$: Observable<unknown>) {
this.store.dispatch(serviceRequestAction({ operation$ }));
}
}
export class ServicesEffects {
serviceRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(serviceRequestAction),
mergeMap((action: ServiceRequestAction) => {
return action.operation$.pipe(
map((result) => {
// send back an action with the result
})
);
})
)
);
}
Usage:
this.servicesStore.dispatch(this.userService.getAll$());
It works well.
But if this observable is a Subject (say MatDialog.open().afterClosed()) it will break the immutable action Ngrx rule.
Because of the inner subscription, the Subject adds an observer into its internal structure, thus breaking the action immutability. It then triggers the Ngrx runtime checks.
Of course I can disable these check, but I am looking for a better away around this. For example, is there a way to clone a Subject ?
Or any other way to allow a Subject into the action payload ?
AFAIK adding a subject to a NgRx Action isn't supported (if you want to keep the runtime checks enabled).
The classic NgRx process is that the effect results in a new action (popular ones are success and failure).

How to properly handle simultaneous persistence actions in Redux?

React application using Redux. A have a combined reducer, consisting of appStateReducer and contestReducer. Each of these two takes care of some part of the application data.
When action is performed, I want not only the respective state to be changed, but I also want to persistently save the new state, so that if the user reloads application page in the browser, the state would be preserved.
My idea is to add third reducer to take care only of save and load actions (each of the two sub-states separately).
Save and load will use IndexedDB, through localbase package. All of the db actions (add, get, update, delete) appear to be synchronous, i.e. there seems to be no real need to implement asynchronous actions. UPDATE: this is wrong, it is asynchronous, just some basic examples ignore it.
I am not sure how to handle the problem properly.
I will need a database connection object, a singleton, initialized once after page is loaded, which should be shared by all save/load actions regardless of which part of the state is to be stored or loaded. That would lead to a separate reducer working only with the db object. If I do this, the db reducer would have to have access to all the other sub-state, which is normally not the case in Redux.
Or, I could implement save and load action in each reducers separately, not a big deal, actually. But how to make the global db object accessible by the reducers?
It is as React application written in typescript and all components are implemented as classes.
You already have access to all data if you are using middleware, Example:
export const requestPost = (id) => (dispatch,getState) => {
// You can make an bank for post and check If data exist or not
const postState = getState().bank.posts.data;
const found = postState?.find((post) => post.id === id);
if (found) {
dispatch({ type: SUCCESS.POST, data: found });
} else {
dispatch({ type: REQUEST.POST });
API.get(`/post/v2?id=${id}`)
.then((res) => dispatch({ type: SUCCESS.POST, data: res.data[0] }))
.catch((err) => errorHandler(err, FAILURE.POST));
}
};
Just make and reducer for saving data on DB or somewhere and read them at the start.

NGRX bulk effect of already defined single effect

So, Im working on an app with a concept of "Plans" and each plan you can add a comment. That part works fine, but it seems to fail and get confused if i try to run this in a loop.
The Action:
export class AddComment implements Action {
readonly type = CommentActionTypes.AddComment;
constructor(public payload: Comment) {}
}
export class AddCommentSuccess implements Action {
readonly type = CommentActionTypes.AddCommentSuccess;
constructor(public payload: Comment) {}
}
Effect
#Effect()
addComment$: Observable<Action> = this.actions$
.ofType<AddComment>(CommentActionTypes.AddComment).pipe(
switchMap(action => this.commentService.addComment(this.disciplineType, action.payload)),
map((comment: any) => new AddCommentSuccess(comment)),
catchError(err => of(new AddCommentFail(err)))
);
Implementation
What im struggling with is firing this off in rapid success/ I have a situation where I want to add a duplicate comment to multiple plans.
saveSet.forEach(x => {
comment.plan_id = x.id;
this.store.dispatch(this.buildAddCommentAction(comment));
});
For reference:
buildAddCommentAction(comment: DisciplineComment) : Action {
return new CommentActions.AddComment(comment);
}
What is Happening
If i have a list of 5 plans, and want to add a duplicate comment to all of them, Im only getting a successful response for the last item in the loop.
Now i know that is overly chatty, that is 5 separate client/service calls. What I cant figure out, its what the prescribed approach to this should be?
1.) A new BulkAddComment Action, effect, etc. Im loathe to do this becuase I have Comments, Concerns (similar in function and need), and one of each for every "discipline". Thatd be about 36 new effects and twice that in actions. A serious refactor is needed.
2.) Modify the actions and effects for 1 or multiple
3.)?
Thanks for input
This is because you're using the switchMap operator which will cancel the current running observable, in your case the service call.
You'll have to use concatMap or mergeMap. If the order is important use concatMap, if not use mergeMap because this will make your service calls in parallel.
For more info, watch this.

Redux Middleware understanding guidance

const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk, api, wsMiddleware, createLogger()),
typeof window === 'object' && typeof window.devToolsExtension !==
'undefined'
? window.devToolsExtension()
: DevTools.instrument(),
),
);
So the above is how you would usually create a store, and then you have your middleware that starts like this:
export default store => next => (action) => {
I did read up the middleware portion from redux.org, but anyone able to better explain it to me what's going on there ?
so is the middleware is receiving the store ? and calling out the next function from the store and than finally using the parameters given as action (in this case). ?
The Redux middleware pipeline can be broken down like this...
store => {...}
The store API is the first argument given to the pipeline. This allows the middleware to get the current state at any point in the pipeline and/or dispatch new actions into the store.
Note: It has many of the same characteristics as the store returned from the createStore function, but it is not the same. Only the dispatch and getState functions are available.
next => {...}
The next argument is a reference to the next middleware in the chain. If there are no more middleware to go, the store handles the action (i.e. pass it into the the reducer).
If next is not called, the action will not make it to the reducer. This can be useful to suppressing something that is not a valid action on it's own, such as a function or a promise, as an error will be raised by Redux if it tries to handle it.
action => {...}
The action argument is the thing that gets dispatched into the store.
{...}
In here is where you will test the action to see if there is something special you want to do with it and whether you will pass it onto the next handler.
An example
For this example, we will create a simplified thunk middleware, explaining how it uses each part of the pipeline.
export default store => next => action => {
// if the action is a function, treat it as a thunk
if (typeof action === 'function') {
// give the store's dispatch and getState function to the thunk
// we want any actions dispatched by the thunk to go through the
// whole pipeline, so we use the store API dispatch instead of next
return action(store.dispatch, store.getState)
} else {
// we're not handling it, so let the next handler have a go
return next(action)
}
}
Functions that conform to the Redux middleware API. Each middleware receives Store's dispatch and getState functions as named arguments, and returns a function. That function will be given the next middleware's dispatch method, and is expected to return a function of action calling next(action) with a potentially different argument, or at a different time, or maybe not calling it at all. The last middleware in the chain will receive the real store's dispatch method as the next parameter, thus ending the chain. So, the middleware signature is ({ getState, dispatch }) => next => action.
The answer was found in the applymiddleware documentation.
https://redux.js.org/docs/api/applyMiddleware.html

Wait for sequence of action with a Redux Observable

I have a use case where I need to wait for a sequence of actions before I dispatch another using Redux Observables. I've seen some similar questions but I cannot fathom how I can use these approaches for my given use case.
In essence I want to do something like so:
action$
.ofType(PAGINATION_CLICKED) // This action occurred.
.ofType(FETCH_SUCCESS) // Then this action occurred after.
.map(() => analyticsAction()); // Dispatch analytics.
I would also like to cancel and start that sequence over again if another action of type FETCH_ERROR fires for example.
Great question. The important point is that action$ is a hot/multicast stream of all actions as they are dispatched (it's a Subject). Since it's hot we can combine it multiple times and they'll all be listening to the same stream of actions.
// uses switchMap so if another PAGINATION_CLICKED comes in
// before FETCH_SUCCESS we start over
action$
.ofType(PAGINATION_CLICKED)
.switchMap(() =>
action$.ofType(FETCH_SUCCESS)
.take(1) // <-------------------- very important!
.map(() => analyticsAction())
.takeUntil(action$.ofType(FETCH_ERROR))
);
So every time we receive PAGINATION_CLICKED we'll start listening to that inner Observable chain that listens for a single FETCH_SUCCESS. It's important to have that .take(1) because otherwise we'd continue to listen for more than one FETCH_SUCCESS which might cause strange bugs and even if not is just generally best practice to only take what you need.
We use takeUntil to cancel waiting for FETCH_SUCCESS if we receive FETCH_ERROR first.
As a bonus, if you decide you want also to do some analytics stuff based on the error too, not only start over, you can use race to indeed race between the two streams. First one to emit, wins; the other is unsubscribed.
action$
.ofType(PAGINATION_CLICKED)
.switchMap(() =>
Observable.race(
action$.ofType(FETCH_SUCCESS)
.take(1)
.map(() => analyticsAction()),
action$.ofType(FETCH_ERROR)
.take(1)
.map(() => someOtherAnalyticsAction())
)
);
Here's the same thing, but using race as an instance operator instead of the static one. This is a stylistic preference you can choose. They both do the same thing. Use whichever one is more clear to you.
action$
.ofType(PAGINATION_CLICKED)
.switchMap(() =>
action$.ofType(FETCH_SUCCESS)
.map(() => analyticsAction())
.race(
action$.ofType(FETCH_ERROR)
.map(() => someOtherAnalyticsAction())
)
.take(1)
);

Resources