How to dispatch an action after catchError in an rxjs epic - error-handling

I am using rxjs and I would like to fire an error handling epic, if an error is caught in my code.
export const exampleLogin = (action$: AnyAction, state$: any) => action$.pipe(
ofType(appActions.APP_START),
withLatestFrom(state$),
concatMap(() => from(getUser())),
pipe(
map(loginActions.loginSuccess),
tap(() => console.log('Silent login success'))
),
catchError((err: any) => {
console.log(err);
loginActions.errorHandler();
return empty();
}
));
The console.log part of the code executes, but my errorHandler function doesn't. If I add the errorHandler to the first pipe, it runs. What am I doing wrong? Thanks!

I think you use the inner pipe in a wrong way. You could do something like:
concatMap(() => from(getUser()).pipe(
map(loginActions.loginSuccess),
tap(() => console.log('Silent login success'))
)),
catchError((err: any) => {
console.log(err);
loginActions.errorHandler();
return empty();
})

Related

initializing flagsmith library inside a function not working

I am trying to use this in my reactjs application: https://docs.flagsmith.com/clients/javascript/
The way it is initalized is as follows:
flagsmith
.init({
environmentID: Config.FLAGSMITH_ENVIRONMENT_ID
})
.then(() => {
flagsmith.startListening(1000);
})
.catch((error) => {
console.log(error)
});
this one works well but I want to wrap it in a function and initalize it from one component only so I did:
function initFlagSmith(){
flagsmith
.init({
environmentID: Config.FLAGSMITH_ENVIRONMENT_ID
})
.then(() => {
flagsmith.startListening(1000);
})
.catch((error) => {
console.log(error)
});
}
but that doesn't work with error u.getItem undefined. Looking at flagsmith, i see u.getItem but also AsyncStorage has the method as well.
any help?
Here is a repo: https://github.com/iconicsammy/flagsmithissue
This was down to a typo, raised a PR here. https://github.com/iconicsammy/flagsmithissue/pull/1

React Native setTimeout - How to clearTimeout

I have a setTimeout call outside of useEffect, how do I clearTimeout when the screen unmounts?
For instance, I have a functional component with this in it...
...
useEffect(() => {
return () => clearTimeout(myTimeout)
}, [])
_getData = () => {
fetch()
.then(data => {
let myTimeout = setTimeout(() => setSomething(!something), 5000)
})
}
So somewhere later in the code I call _getData() - I do not want this to run with useEffect when the page first loads, only when certain action is taken. After I get the data I set the timeout. But the useEffect is not aware of this timeout.
I have tried setting the timeout like this...
_getData...
setTimeoutVar(setTimeout(() => setSomething(!something), 5000))
useEffect...
return () => clearTimeout(setTimeoutVar)
I have tried a few other weird ideas, nothing is working, I can't figure this one out.
Thoughts?
All day working on this - write a question on stackoverflow and figure it out in two minutes. Crazy!
The answer to this question is to set a variable to false, then, change the variable when you get the data back. Then have a separate useEffect() function that only deals with this. When the variable changes it runs. If the variable is true - sets the timeout and the useEffect function returns the clearTimeout...
const [refresh, setRefresh] = useState(false)
useEffect(() => {
let timeoutVariable
if(refresh){
timeoutVariable = setTimeout(() => setRefresh(false), 5000)
}
return () => clearTimeout(timeoutVariable)
}, [refresh])
_getData = () => {
fetch()
.then(data => {
setRefresh(true)
})
}

RxJS HTTP request error handling

In my project, I use RxJS to handle HTTP request. I came into a confusing point about the error handling part as following:
.switchMap((evt: any) => {
return http.getComments(evt.params)
.map(data => ({ loading: false, data }))
.catch(() => {
console.log('debugging here');
return Observable.empty();
});
})
in the above code, inside the switchMap operator, I use the http.getComments function to send request, which is defined by myself as following:
getComments(params) {
return Observable.fromPromise(
this.io.get(path, { params })
);
}
in this function, I use fromPromise operator convert the returned Promise to observable.
The problem is when HTTP request failed, the catch operator inside switchMap can not work, the debugging console can't output. So what's wrong with my code.
Do you really need to catch the error inside the switchMap anyway? You can handle your error in your source if you want.
.switchMap((evt: any) =>
http.getComments(evt.params).map(data => ({ loading: false, data }))
})
.subscribe(console.log, console.error);
Any way, your source code does not look to have any error, maybe your promise is not been rejected when the http fails and is just resolving with an error as a response (this is a guess because I've seen that before)
Your code should work. Here a simplified simulation, where http calls are substituted by a function which raises an error.
import {Observable} from 'rxjs';
function getComments(params) {
return Observable.throw(params);
}
const params = 'abc';
Observable.of(null)
.switchMap(_ => {
return getComments(params)
.map(data => ({ loading: false, data }))
.catch(err => {
console.log('debugging here', err);
return Observable.empty();
});
})
.subscribe(
console.log,
error => console.error('This method is not called since the error is already caught', error),
() => console.log('DONE')
)

redux-observable, How to do an operator like promise.all()?

I have two async requests, want to write a epic do the job like promise.all()
const fetchData1 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data1.json').map((data: any) => {
return requestData1Success(data);
})
);
const fetchData2 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data2.json').map((data: any) => {
return requestData2Success(data);
})
)
const requestAllDataEpic = (action$: ActionsObservable<any>, store: any) => {
return action$.ofType(t.REQUEST_ALL_DATA)
.map((action) => action.payload)
.switchMap((names: string[]) => {
console.log(names);
return Observable.forkJoin([
fetchData1()(action$, store),
fetchData2()(action$, store)
])
.map((results: any[]) => {
const [action1, action2] = results;
requestData1Success(action1);
requestData2Success(action2);
});
});
};
But when I dispatch the action, the console give me an error:
Uncaught TypeError: Cannot read property 'type' of undefined
I think the reason is I do not give the middleware an action object, but undefined.
How can I do this correctly?
In the provided example, you are not actually returning your two actions, you're returning nothing:
.map((results: any[]) => {
const [action1, action2] = results;
// v----- not returning either of these
requestData1Success(action1);
requestData2Success(action2);
});
map can't used to emit two actions sequentially because it's 1:1 not 1:many (mergeMap, switchMap, concatMap, etc are 1:many). However, in your example you are already converting the responses to the actions inside your fetchData helpers--doing it again would wrap an action inside another action, not what you want. This looks like a bug when you were refactoring.
Other than that, it's actually not clear what you intended to do. If you have further questions you'll need to describe what you want you'd like to achieve.

redux-observable to get current location

I'm trying to use react native Geolocation to getCurrentPosition and then as soon as the position is returned, use react native geocoder to use that position to get the location. I'm using redux-observable epics to get all of this done.
Here are my two epics:
location.epic.js
import { updateRegion } from '../map/map.action'
import Geocoder from 'react-native-geocoder'
export const getCurrentLocationEpic = action$ =>
action$.ofType(GET_CURRENT_LOCATION)
.mergeMap(() =>
Observable.fromPromise(Geocoder.geocodePosition(makeSelectLocation()))
.flatMap((response) => Observable.of(
getCurrentLocationFulfilled(response)
))
.catch(error => Observable.of(getCurrentLocationRejected(error)))
)
export const getCurrentPositionEpic = action$ =>
action$.ofType(GET_CURRENT_POSITION)
.mergeMap(() =>
navigator.geolocation.getCurrentPosition(
(position) => Observable.of(
updateRegion(position),
getCurrentLocation(position)
),
error => Observable.of(getCurrentPositionRejected(error)),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
).do(x => console.log(x))
).do(x => console.log(x))
As soon as the app starts, this code executes:
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => { })
store.dispatch(fetchCategories())
store.dispatch(getCurrentPosition())
}
fetchCategories() is an action that has an epic too, but that is working. dispatching the getCurrentPosition() action runs through the epic above. The only output that I can see is that my reducer handles getLocationRejected() as it console logs this:
there was an issue getting your current location: Error: invalid position: {lat, lng} required
at Object.geocodePosition (geocoder.js:15)
at MergeMapSubscriber.project (location.epic.js:17)
at MergeMapSubscriber._tryNext (mergeMap.js:120)
at MergeMapSubscriber._next (mergeMap.js:110)
at MergeMapSubscriber.Subscriber.next (Subscriber.js:89)
at FilterSubscriber._next (filter.js:88)
at FilterSubscriber.Subscriber.next (Subscriber.js:89)
at Subject.next (Subject.js:55)
at Object.dispatch (createEpicMiddleware.js:72)
at Object.dispatch (devTools.js:313)
Here is my reducer:
const searchPage = (
initialLocationState = initialState.get('searchForm').get('location'),
action: Object): string => {
switch (action.type) {
case GET_CURRENT_LOCATION_FULFILLED: {
return action.payload
}
case GET_CURRENT_LOCATION_REJECTED: {
console.log('there was an issue getting your current location: ',
action.payload)
return initialLocationState
}
case GET_CURRENT_POSITION_REJECTED: {
console.log('there was an issue getting your current position: ',
action.payload)
return initialLocationState
}
default:
return initialLocationState
}
}
Is there anything obvious I am doing wrong? My attempt to debug by adding .do(x => console.log(x)) does nothing, nothing is logged to the console. updateRegion() never does fire off because that dispatches an action and the reducer UPDATE_REGION never executes. But the execution must make it into the success case of getCurrentPosition() eg:
(position) => Observable.of(
updateRegion(position),
getCurrentLocation(position)
),
must execute because the getCurrentLocation(position) does get dispatched.
Where am I going wrong?
What would be your technique for using an epic on a function which takes a callback function? getCurrentPosition() takes a callback and the callback handles the payload. Basically if you remove Observable.of( from inside getCurrentPosition(), that's how getCurrentPosition() is correctly used - and has been working for me without redux-observable.
Wrapping anything in a custom Observable is fairly simple, very similar to creating a Promise except Observables are lazy--this is important to understand! RxJS Docs
In the case of geolocation, there are two main APIs, getCurrentPosition and watchPosition. They have identical semantics except that watchPosition will call your success callback every time the location changes, not just a single time. Let's use that one since it's natural to model it as a stream/Observable and most flexible.
function geolocationObservable(options) {
return new Observable(observer => {
// This function is called when someone subscribes.
const id = navigator.geolocation.watchPosition(
(position) => {
observer.next(position);
},
error => {
observer.error(error);
},
options
);
// Our teardown function. Will be called if they unsubscribe
return () => {
navigator.geolocation.clearWatch(id);
};
});
}
geolocationObservable({ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 })
.subscribe(
position => console.log(position),
e => console.error(e)
);
// will log every time your location changes, until you unsubscribe
Since it's now an Observable, if you only want the current location you can just do .take(1).
So using it inside your epic might be like this
// If you want, you could also use .share() to share a single
// underlying `watchPosition` subscription aka multicast, but
// that's outside the scope of the question so I don't include it
const currentPosition$ = geolocationObservable({
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 1000
});
export const getCurrentPositionEpic = action$ =>
action$.ofType(GET_CURRENT_POSITION)
.mergeMap(() =>
currentPosition$
.take(1) // <----------------------------- only the current position
.mergeMap(position => Observable.of(
updateRegion(position),
getCurrentLocation(position)
))
.catch(error => Observable.of(
getCurrentPositionRejected(error)
))
);
As a side note, you might not need to dispatch both updateRegion() and getCurrentLocation(). Could your reducers just listen for a single action instead, since they both seem to be signalling the same intent?