I'm trying to catch an error on the effect of a specific action. Everything is working fine, whether when the call succeeds or fails. However, I think the code is very verbose, and could be improved on, specially:
empty()
but I bieleve should be returning the SavedReplyDeleteFailed
action. When doing so the whole effects breaks and does not compile, not sure on whats the best practice for handling this.tap
and dispatch
instead of returning the actions from the map/mergeMap call backs. When I do so the effects compilation breaks as well.How I can apply the above improvements?
deleteSavedReply = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteRequested),
mergeMap( action => {
this.store.dispatch( SavedRepliesActions.DeleteLoading({ isLoading: true }) );
return this.savedRepliesService.deleteSavedReply(action.id).pipe(
tap(res => {
this.store.dispatch( SavedRepliesActions.SavedReplyDeleteSucess({ id: action.id }));
this.toastr.success( 'Saved Reply has been successfully deleted');
}),
catchError((error) => {
this.store.dispatch( SavedRepliesActions.SavedReplyDeleteFailed({message: error.message}) );
this.toastr.error( error.message, 'Something went wrong');
this.store.dispatch( SavedRepliesActions.DeleteLoading({ isLoading: false }) );
return empty();
})
);
}),
map(() => {
return SavedRepliesActions.DeleteLoading({ isLoading: false });
}),
));
You can opt for a more reactive way (using RxJS powerful), and so simplify a little your code with something like this:
deleteSavedReply$ = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteRequested),
mergeMap(action => this.savedRepliesService.deleteSavedReply(action.id).pipe(
switchMap(res => [
SavedRepliesActions.SavedReplyDeleteSucess({ id: res.id }),
UiActions.showToastrSuccess('Saved Reply has been successfully deleted')
]),
catchError(error => of(
SavedRepliesActions.SavedReplyDeleteFailed({ message: error.message }),
UiActions.showToastrError(error.message, 'Something went wrong')
]))
))
))
To do this, in the redux
mindset, you should :
Create 2 new actions UiActions.showToastrSuccess
and UiActions.showToastrError
to show a message Toastr. Displaying a message is a could target for an action.
Use reducer
to your loading
flag. It's a slice of your state, so should change its value inside a reducer in place of the effect.
For example :
on(SavedRepliesActions.SavedReplyDeleteFailed, (state) => ({
...state,
loading: false
})),
on(SavedRepliesActions.SavedReplyDeleteRequested, (state) => ({
...state,
loading: true
})),
Note that another option (or improvment...) could be to dispatch Toastr actions, inside SavedReplyDeleteSucess
action.
For example :
deleteSavedReply$ = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteRequested),
mergeMap(action => this.savedRepliesService.deleteSavedReply(action.id).pipe(
map(res => SavedRepliesActions.SavedReplyDeleteSucess({ id: res.id }))
catchError(error => of(SavedRepliesActions.SavedReplyDeleteFailed({ message: error.message })))
))
))
savedReplyDeleteSucess$ = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteSucess),
map(_ => UiActions.showToastrSuccess('Saved Reply has been successfully deleted'))
))
savedReplyDeleteFailed$ = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteFailed),
map(message => UiActions.showToastrError(error.message, 'Something went wrong'))
))
The advantage here is that you can make your code evolving, by adding some extra effects in case of success or failure. for example:
savedReplyDeleteFailed$ = createEffect(() => this.actions$.pipe(
ofType(SavedRepliesActions.SavedReplyDeleteFailed),
switchMap(message => [
UiActions.showToastrError(error.message, 'Something went wrong')),
SystemActions.sendTraceError(error)
))
Hope it will help.