redux-observable to get current location - react-native

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?

Related

How to dispatch two actions in one epic, which could be in the same or in another reducer

I have two ducks (ui and backend) with epics in them.
I need to trigger two actions after finishing backend operations
One of these actions reside in the backend duck, the other in the ui duck.
I started with the background action and things worked as expected.
Adding the second action leads me to issues, as I can reach the action (console logs correctly), but not the reducer (no log)
The challenge I'm trying to solve is:
Kicking off two actions in one epic
dispatching an action in another reducer
My code looks similar to this:
the backendDuck's epic:
fetchFooEpic : (action$, store) =>
action$.pipe(
operators.filter(action => action.type === types.LOAD),
// start added section for second call
operators.switchMap(action => {
const response = operators.from(fetchSomeUrl(action))
.pipe(
operators.of(uiDuck.actions.fetchUserFulfilled(response.props)),
),
operators.catchError(err => {
console.error('Error happened!', err.message)
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
return response
}),
// start added section for second call
// original first call
operators.map(a => ({ type: types.ENDACTION, payload: a.payload })),
operators.catchError(err => {
console.error('Error happened!', err.message)
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
)
the uiDuck:
export actions={
...
fetchUserFulfilled: (value) => {
console.log('hello from action')
return ({ type: types.FETCHUSERFULFILLED, payload: value })
},
...
}
...
export default function reducer(state = initialState, action) {
switch (action.type) {
case types.FETCHUSERFULFILLED:
console.log('hello from reducer')
return {
...state,
user: action.payload,
}
...
Turns out I was combining the two calls in the wrong way.
For being able to pipe along, the piped observable needs to return an observable again.
When mapping to another redux-action, it seems to me that it doesn't return an observable (?) thus, the call needs to happen for all desired redux-actions at the same location (eg with concat)
For the sake of completeness I strive to explain all parts of the code in comments
import * as operators from 'rxjs'
fetchFooEpic : (action$, store) =>
action$.pipe(
operators.filter(action => action.type === types.LOAD), // Filter
operators.switchMap(action => // restart inner actions on each call
operators.from(fetchSomeUrl(action)) // creating observable from result
.pipe( // starting new flow on observable (self)
//operators.tap(a => console.log('Now running fetchfooepic 2', a)), // dedicated location for sideeffects
operators.switchMap( // restart inner actions on each call
(response) => operators.concat( // Kicking off several actions sequentially (merge() would do that in parallel)
operators.of(uiDuck.actions.fetchUserFulfilled(response)), // addressing the redux action in other reducer
operators.of(({ // addressing the redux action via the type in this duck (ENDACTION is controlled by epics only, no action exists for it)
type: types.ENDACTION,
payload: response
}})),
)),
operators.catchError(err =>{
console.error('Shit happens!', err.message) // errorhandling
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
)
)
),
Generally the functions are documented with some (more or less understandable) examples in
https://rxjs.dev/api/index/function/

Unable to set useState variable in async method and console log it

Im working with my friends on a app project and we find an issue many times when we tring to set a use state and the console log the variable, i've looking for a solution in the website and saw that the reason is that the usestate is an async awiat which means the variable that i set in the use state isn't immidatly set in it, so i tried many solution that i found in the websites but none of them work for me.
in the screenShot you can see that the json variable is in console log before and the set varaible doesn't show after the setActiveUser , any help?
Thanks!
If you want to do something upon setting state then your best bet is to use the useEffect hook from React and adding your state in the dependency array. This will then call the function in your useEffect every time the state changes. See below a rough example:
import { Text } from 'react-native';
const MyComponent = () => {
const [activeUser, setActiveUser] = useState(null);
useEffect(() => {
// This should log every time activeUser changes
console.log({ activeUser });
}, [activeUser]);
const fetchAuthentication = async user => {
var flag = false;
await fetch('/api/authUser/', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
})
.then(res => {
res.ok && (flag = true);
return res.json();
})
.then(json => {
if (flag) {
setActiveUser(json);
}
})
.catch(error => console.error(error));
return flag;
};
return <Text>Hi</Text>;
};
Full documentation: https://reactjs.org/docs/hooks-effect.html

Huawei Location Kit for React Native, addFusedLocationEventListener method doesn't trigger callback

Setup the huawei location kit for getting device position overtime when apps in use, followed the setup from https://developer.huawei.com/consumer/en/doc/HMS-Plugin-Guides-V1/config-agc-0000001050197382-V1
we don't have real huawei device, we're using cloud debugging
Try implement to watch the gps location overtime with all these syntax
// ------ Parent ------
// this put on the parent useEffect
HMSLocation.LocationKit.Native.init()
.then(() => console.log('----------Success Initialize----------'))
.catch((err) => alert(err.message))
// ------ Child ------
const stopWatchingLocation = () => {
if (hasHms) {
HMSLocation.FusedLocation.Events.removeFusedLocationEventListener(
(res: LocationResult) => console.log('remove add listener', res),
)
}
}
const startWatchingLocation = async () => {
if (hasHms) {
HMSLocation.FusedLocation.Native.requestLocationUpdatesWithCallbackEx(
hwGeolocationOptions,
)
.then((res) => console.log('success request', res))
.catch((error) => console.log('failed request', error))
HMSLocation.FusedLocation.Events.addFusedLocationEventListener(
(res: LocationResult) => console.log('result', res.lastHWLocation)
)
}
}
// implementation of add & remove event listener
useEffect(() => {
startWatchingLocation() // inside here invoke addFusedLocationEventListener
return stopWatchingLocation // inside here invoke, cleanup function removeFusedLocationEventListener
}, [])
The code successfully invoke the init, requestLocationUpdatesWithCallbackEx, but console log from addFusedLocationEventListener never invoke
Already turn on hms core app permission for location, hasPermission also returned true
Tried the locationRequest options from problem with react native #hmscore/react-native-hms-location comments, still not working
How we can fix these??
I think it might be a usage issue. The function of addingFusedLocationEventListener is to add FusedLocationEvent Listener. This function is triggered only when FusedLocationEvent happen.
In your description, delete removeFusedLocationEventListener after addFusedLocationEventListener, the added listener is also deleted.
In addition, you are advised to use independent functions instead of directly defining them in input parameters.
handleLocationUpdate = (locationResult) => { console.log(locationResult); this.setState({ locationCallbackResult: locationResult }); }
requestLocationCallbackWithListener = () => {
HMSLocation.FusedLocation.Native.requestLocationUpdatesWithCallbackEx(locationRequest)
.then((res) => this.setState({ reqCode: res.requestCode }))
.catch((err) => alert(err.message));
HMSLocation.FusedLocation.Events.addFusedLocationEventListener(this.handleLocationUpdate);
this.setState({ autoUpdateEnabled: true });
};

React-Native using nested async not working properly

I have methods that benefit each other and I want one of them to start after the other, but it doesn't work exactly the way I want it. In another question, I used async and await in a normal method, but I did not manage to use google APIS and nested functions.
async componentDidMount() {
await this.getCityList();
console.log(this.state.cities)
await this.currentLocation();
await this.getVenueInformation();
}
At this point, the first function works correctly and after passing the required ones, it goes to the 2nd method. In the same way, I have to put the inputs of the 2nd method into the 3rd method, but I failed.
Current location method:
currentLocation() {
Geocoder.init(...);
Geolocation.getCurrentPosition((position) => {
this.getCurrentLoc(position.coords.latitude, position.coords.longitude),
this.setState({
region: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
},
error: null,
});
}, (error) => this.setState({ error: error.message }), { enableHighAccuracy: false, timeout: 200000, maximumAge: 1000 });
}
I want my location with the above method, then I get the address information with getcurrentloc method and I plan to show some places in the vicinity in accordance with that information. However, I could not make this system work sequentially and my address is always null.
getCurrentLoc(lat, longt) {
Geocoder.from(lat, longt)
.then(json => {....
this.setState({...},
this.getLocIDS(cityName, districtName, neighborhoodName)
);
})
.catch(error => console.warn(error));
}
at this point, I get the address from the database by address to pull the id information.
getLocIDS(cityName, districtName, neighborhoodName) {
let link = ..;
fetch(link)
.then(response => response.json())
.then(res => {
this.setState({,,,,,,})
})
.catch(error => console.warn(error));
}
lastly, I want to capture the venue information, but at this point the data is entered as -1 in the database. -1 is my initial state value. I'm sure all methods work correctly, but since the processes don't run sequentially, my space search method works before updating the data in the previous functions state.
getVenueInformation() {
let link = ...
fetch(link)
.then(response => response.json())
.then(venues => {
this.setState({
markers: venues,
isLoading: false,
})
})
};
database input return: >>>>> SELECT * FROM mekanlar WHERE mekanlar.mahalle_no="-1";
You don't show how you pass the information from one method to the other.
If you do it by use of this.state, that is where your problem is.
this.setState will not update the state synchronously. So you might not have the right value in this.state in your next function call.
To make sure the data flow is correct, it makes more sense to return the values explicitly, even if you do setState internally.
async componentDidMount() {
const cityList = await this.getCityList();
console.log(cityList)
const location = await this.currentLocation(cityList);
const venueInformation = await this.getVenueInformation(location);
}

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)
})
}