Redux-Observable multiple actions in single epic - redux

I'm new to this and there are several similar questions such as redux-observable - dispatch multiple redux actions in a single epic but I can't see how they apply to my usecase.
I'm using a Subject to emit multiple events based on processing a file and uploading to a server
export function UploadSceneWithFile(scene){
const subject$ = new Subject()
FileToScenePreview(scene,scene.file).then(res => subject$.next(res))
const uploader = new S3Upload({
....
onError:()=>subject$.next('error'),
onProgress: (val)=> subject$.next(val),
onFinishS3Put: ()=>subject$.next('complete'),
})
uploader.uploadFile(scene.file)
return subject$
}
A want to write an epic that captures these events and dispatches actions based on the data coming back.
ie. something like this
export function uploadSceneFile(action$) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.subscribe(res => {
console.log(res)
if (!res.thumbName) {
return { type: UPLOAD_SCENE_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_SUCCESS, payload: res }
}
if (!res.value) {
return { type: UPLOAD_SCENE_THUMB_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_THUMB_SUCCESS, payload: res }
}
})
)
}
I'm getting this error:
TypeError: You provided an invalid object where a stream was expected.
You can provide an Observable, Promise, Array, or Iterable.
i can console log the results OK, but I'm not dispatching any actions.. any ideas how I go about this?

What's happening is that you're returning a subscription inside your mergeMap, instead of an Observable that it expects. Instead of using subscribe it looks like you want to use map
export function uploadSceneFile(action$) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.map(res => { // <------------------ map, not subscribe
console.log(res)
if (!res.thumbName) {
return { type: UPLOAD_SCENE_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_SUCCESS, payload: res }
}
if (!res.value) {
return { type: UPLOAD_SCENE_THUMB_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_THUMB_SUCCESS, payload: res }
}
})
)
}
In redux-observable you will almost never call subscribe yourself, instead composing Observables. The only time you'd use it is mostly with custom operators that aren't super common.

Related

How to return boolean in Angular

I have a backend method called LikeExists() to verify if a certain user has liked a certain post.
public async Task<bool> LikeExists(int postId)
{
var post = await _postRepository.GetPostByIdAsync(postId);
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
if (_context.Likes.Where(i => i.PostId == post.Id && i.UserId == user.Id).FirstOrDefault() != null) return true;
return false;
}
The method works fine in Postman, but it does not do the job in Angular. If a user presses a like button I first want to check if this user has already liked this post. If he has, he will unlike it and the like will be deleted from the database. If he hasn't liked it, he will like it and the like will be saved in the database.
likeExists(){
this.postService.likeExists(this.post.id).subscribe((response: boolean) =>{
this.like = response;
});
}
likePost() {
if(this.likeExists){
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
}
The problem is it always enters the if{} clause and never the else{} clause. The method below returns an Observable. I think the problem is that it must return a boolean. How can I make this work?
This is the method in the postService:
likeExists(postId: number) {
return this.http.get(this.baseUrl + 'like/exists/' + postId);
}
Try avoiding nested subscription since it will result in an unreadable and hard to maintain code, use rxjs pipes with operators instead, try something like this:
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(
switchMap(likeExists => {
if (likeExists) {
// delete like
return this.postService.deleteLike(id);
}
// otherwise addlike
return this.postService.addLike(id);
})
).subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
or even shorter
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(switchMap(
liked => liked ? this.postService.deleteLike(id) : this.postService.addLike(id)}))
.subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
The problem is that this.http.get is asynchronous, which means that likeExists returns before this.like is being set. You need to wait for the value to be returned in your observable. Refactor your code to something along these lines:
likePost() {
// Check to see if like exists and wait for response from server
this.postService.likeExists(this.post.id).subscribe((response: boolean) => {
this.like = response;
if (this.like) {
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
});
}
Also, this is a nice guide to asynchronous concepts in general. And the RxJS docs have a bunch of helpful information to get started.

In Redux-saga, how to call a saga in one saga's fetch?

In my React app, there is a Saga calling backend API to retrieve some chart data. Please read the source code
function fetchData () {
return fetch(`${config.base}dashboard_charts.json`)
.then((response) => {
if (response.status === 200) {
return response.json();
} else if (response.status === 403) {
return 'logon';
}
});
}
function * getData (action) {
try {
const charts = yield call(fetchData);
if (charts === 'logon') {
yield logon();
} else {
yield put({ type: UPDATE_DASHBOARD_CHART, charts });
}
} catch (error) {
yield put({ type: UPDATE_DASHBOARD_CHART, charts: [] });
}
}
function * logon (action) {
yield auth();
}
export default function * portfolio () {
yield [
takeEvery(FETCH_DASHBOARD_CHART, getData)
];
};
There is a checking against the http response status in the function fetchData, if the status is 200 then return the response directly. But if the server side returns 403, it means the client needs to be authenticated, thus the program will goes to auth() and perform logon.
However, the http response status code checking is somehow a general functionality applied to all API calls. So I don't want to repeat this kind of code in every saga. For this purpose, I wrote a service 'responseHandler' to group the response code checking inside like this:
export default function responseHandler (resp) {
if (resp.status === 401 || resp.status === 403) {
yield auth();
} else if (resp.status === 200) {
} else {
console.log('error handling');
}
};
And it will be called inside the the fetchData
return fetch(`${config.base}dashboard_charts.json`)
.then((response) => {
responseHandler(response);
});
But this approach is never working. 'yield auth()' is invalid in the responseHandler.
Can anyone suggest how to better design the code for this case ?
Maybe it makes a sense to organize logic several?
First, the fetch-wrapper can be modified so that in case of origin of HTTP of a response code which doesn't meet expectation for formation of successful result, to execute transition to catch-section. It will allow to save the fetchData function in the form of pure Promise without entering of generator logic into it.
Secondly the essence of the auth and logon functions isn't especially clear. If by results of such action the form for login be generated, then realize it through appropriate redux action. If transition to other page is required, use react-redux-router.
function fetchData () {
return fetch(`${config.base}dashboard_charts.json`).then(response => (
(response.status === 200) ? response.json() : Promise.reject('logon')
));
}
function* getData (action) {
try {
const charts = yield call(fetchData);
yield put({ type: UPDATE_DASHBOARD_CHART, charts });
} catch (error) {
yield put({ type: UPDATE_DASHBOARD_CHART, charts: [] });
if(error.message === 'logon') {
yield put({ type: PERMORM_AUTORIZE });
}
}
}
export default function * portfolio () {
yield [ takeEvery(FETCH_DASHBOARD_CHART, getData) ];
};
And is your logic is more complex, just use fork from redux-saga. It allows perform more complex tasks.

NGRX: error callback not reached

I'm dispatching an action when a form is submitted, so then checkPasswd is reached:
public checkPasswd():void {
this.store$.dispatch({ type: 'USER_REDUCER_USER_LOGIN', payload: { username: this.form.value.mail, password: this.form.value.passwd } });
}
on ngOnInit - constructor I've created this subscription to Store<IStore>:
private user$: Observable<IUser>;
private userSub: Subscription;
constructor(private store$: Store<IStore>)
{
this.user$ = this.store$.select(state => state.user).filter(user => user.id != null);
}
ngOnInit():void {
this.userSub = this.user$.subscribe(
(user: IUser) => {
this.router.navigate(['/app']); (((2)))
},
(error: any) => {
this.addAlert(error.message); (((1)))
}
);
}
I'm checking that (((1))) is reached when an error occurs. Nevertheless, it's never reached when an error appears.
I'm using efffects:
#Effect({ dispatch: true })
userLogin$: Observable<Action> = this._actions$
.ofType('USER_REDUCER_USER_LOGIN')
.switchMap((action: Action) =>
this._userService.checkPasswd(action.payload.username, action.payload.password)
.map((user: any) => {
return { type: 'USER_REDUCER_USER_LOGIN_SUCCESS', payload: user };
})
.catch((err) => {
return Observable.of({ type: 'USER_REDUCER_USER_LOGIN_FAILED', payload: { error: err } });
})
);
As you can see, in my effect I'm trying to call to my service, and if it gets an error I'm returning Observable.of({ type: 'USER_REDUCER_USER_LOGIN_FAILED', payload: { error: err } });.
So, after that, this reducer is called and reached:
public static USER_LOGIN_FAILED = `${UserReducer.reducerName}_USER_LOGIN_FAILED`;
private static userLoginFailed(sourcesRdx, type, payload) {
return Object.assign(<IUser>{}, sourcesRdx, payload);
}
Nevertheless, (((1))) is not reached, neither (((2))). So, the call stack seems to not reach my subscription...
Any ideas?
First: your select filters out any emission that do not contain a user-id, which I assume would be the case when a login fails - this means that absolutely no data will be emitted beyond this point unless there is a valid user with an id in the store.
this.user$ = this.store$.select(state => state.user).filter(user => user.id != null);
Second: The store.select will never emit any errors, since it is a perpetual stream - if errors where emitted, that would break the stream (this is one of the RxJS core-principles: Errors will stop and finalize a stream).
So the behavior seems to be expected to me.
How to handle this? - If you are looking to persist errors in your ngrx-store, then you would have to implement a field for that and add a select to get errors and display them - if you do not want to go through the store, you could add an error-alert-handler to the effect directly.
Also have a look at the ngrx-example-app for best-practices: https://github.com/ngrx/example-app

redux-promise with Axios, and how do deal with errors?

So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.

Angular2 - How to chain async service calls (http requests) in a component?

I have a component which first need to call a service that POST something. Then in the same component I want to wait until the POST is done, to call another service which GETs data.
How can I make the GET call wait for the POST call to finish?
In new-version.component.ts:
private createNewVersion(value) {
...
// create new version, then call on all available versions
// POST call
this._newVersionService.createNewVersion(vnr);
// GET call
this._versionService.getAvailableVersions();
...
}
In new-version.service.ts:
export class NewVersionService {
response$: Subject<any>;
constructor(private _http: Http) {
this.response$ = new BehaviorSubject<any>(null);
}
public createNewVersion(versionNr) {
this._http.post('http://localhost:8080/services/' + versionNr, null, {
method: 'POST',
})
.subscribe(response => {
this.response$.next(response.status);
},
error => console.error(error));
}
Thanks!
When a call returns a Promise chain the calls with
someFunction() {
return returnsPromise()
.then(result => doSomethingNext())
.then(result => doSomethingAfterThat());
}
Ensure you have a return that returns the Promise of that chain so the caller of someFunc() also has a chance to time additional work to execute after doSomethingAfterThat() is completed.
When a call returns an Observable then use the complete callback
someFunction() {
return returnsObservable()
.subscribe(
event => doForEachEvent(),
error => handleError(),
() => doSomethingNext()
.then(result => doSomethingAfterThat());
}
doSomethingNext() is executed after the last event and doSomethingAfterThat() is again chained with then() to show how to mix observable and promise. doSomething().
You should be able to concat to achieve sequence, and reduce to collect the emitted values:
var a = this._newVersionService.createNewVersion(vnr);
var b = this._versionService.getAvailableVersions();
Rx.Observable.concat(a, b).reduce((acc:Array<any>, x:any) => {
acc.push(x); return acc;
}, []).subscribe(t=> {
var firstEmitted = t[0];
var secondEmitted = t[1];
});
You can do like this:
Change createNewVersion to:
public createNewVersion(versionNr) {
return this._http.post('http://localhost:8080/nod_inspection_plugin/services/' + versionNr, null, {
method: 'POST',
});
}
Then in your call:
this._newVersionService.createNewVersion(vnr).subscribe(response=> {
this._versionService.getAvailableVersions();
}, error => console.error(error));
Another way to do the same is to subscribe in the new-version.component.ts and call you GET request from within the POST request i.e check whether your POST request is done Correctly or not
if yes POST is done Properly then call you GET request. As below:
In new-version.component.ts:
private createNewVersion(value) {
...
// create new version, then call on all available versions
// POST call
this._newVersionService.createNewVersion(vnr)
.subscribe((res) => {
if(res){
console.log(res);
if (---Post request done properly check via status or something else here----{
CALL YOUR GET REQUEST HERE.....
// GET call
this._versionService.getAvailableVersions();
}
else {
DO something else whatever you want....
}
}
});
...
}
In new-version.service.ts:
export class NewVersionService {
response$: Subject<any>;
constructor(private _http: Http) {
this.response$ = new BehaviorSubject<any>(null);
}
public createNewVersion(versionNr) {
this._http.post('http://localhost:8080/nod_inspection_plugin/services/' + versionNr, null, {
method: 'POST',
})
.map(response => {
return [{status: response.status, json: response.json()}];
},
error => console.error(error));
}
for more info related to http request you can read here.
Better use switchMap() here.
const versions$ = this._newVersionService.createNewVersion(vnr)
.switchMap(response => this._versionService.getAvailableVersions());
versions$.subscribe(response2 => this.versions = response2)
But the problem will be if you make another POST request before first has been resolved, the previous request will get cancelled.

Resources