Hello everyone who sees that question
I need help in that and full of hope that someone is gonna help
I am trying to get the exact location for the user to pass it finally in some other functionalities. I am using Expo init and expo-location
while using (reversegeocodeAsync({})) for the first render it's giving me the correct location but while testing it's crashing and giving an error and even works it's not making the data like after setting state it's not being available globally to use it
I tried different ways
First : use all the functions inside the same page but it doesn't work
import React, {useState, useEffect, useMemo} from 'react';
import {View, Text, StyleSheet, FlatList } from 'react-native';
import { NavigationEvents } from 'react-navigation';
import TimeApi from '../compnents/TimeApi';
import * as Location from 'expo-location';
const LocationScren = () => {
const [time, setsTime] = useState({});
const [errorMsg, setErrorMsg] = useState('');
const [location, setLocation ] = useState(null);
const [city, setCity ] = useState();
const getLocation = async () => {
let {status} = await Location.requestPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Access to Location denied');
}
const location = await Location.getCurrentPositionAsync({});
setLocation(location)
}
const getCity = async () => {
const place = await Location.reverseGeocodeAsync({
latitude : location.coords.latitude,
longitude : location.coords.longitude
});
place.find( p => {setCity(p.city);
})
}
const getTime = async () => {
const response = await TimeApi.get(`/${city}.json`);
setTime(response.data);
}
useEffect(() => {
getTime(), getLocation(), getCity();
} , []);
console.log(time);
console.log(location);
console.log(city);
return (
<View>
<FlatList
data = {time.items}
keyExtractor = {time => time.first}
renderItem = {({item}) => {
return (
<View>
<Text> {item.first} </Text>
<Text> {item.secnd} </Text>
<Text> {item.third} </Text>
<Text> {item.fourth} </Text>
<Text> {item.fifth} </Text>
<Text> {item.sixth} </Text>
</View>
);
}}
/>
{errorMsg ? <Text> {errorMsg} </Text> : null }
</View>
);
}
const styles = StyleSheet.create({});
export default LocationScren;
in here in the first render it's giving errors, then work , then giving that error ( null is not an object (evaluating 'location.coords')] )
Then I create a context file and added my functions and still getting the same error exactly
import createDataContext from './createDataContext';
import * as Location from 'expo-location';
const mwaqeetReducer = (state,action) => {
switch(action.type) {
case 'get_location' :
return action.payload;
case 'add_error' :
return {...state, errorMessage : action.error};
case 'get_city' :
return { cityName : action.payload};
default:
return state;
}
}
const getLocation = dispatch => async () => {
let {status} = await Location.requestPermissionsAsync();
if (status === !'granted') {
dispatch({type: 'add_error' , error : 'Permission to access location denied'});
}
let location = await Location.getCurrentPositionAsync({});
dispatch({type : 'get_location' , payload : location});
console.log(location);
}
const getCity = dispatch => async () => {
let keys = {
latitude : location.coords.latitude,
longitude : location.coords.longitude
}
const place = await Location.reverseGeocodeAsync(keys);
place.find( p => p.city);
dispatch({type : 'get_city' , payload : place});
console.log(place);
}
export const {Provider, Context} = createDataContext(
mwaqeetReducer, {
getLocation, getCity
} , {
errorMessage : '', location : {}, cityName : ''
}
)
so, please I need help to get over that.
You can try something like this.
useEffect(() => {
runFunction();
} , []);
const runFunction = async () => {
let {status} = await Location.requestPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Access to Location denied');
}
const location = await Location.getCurrentPositionAsync({});
setLocation(location)
const place = await Location.reverseGeocodeAsync({
latitude : location.coords.latitude,
longitude : location.coords.longitude
});
let city;
place.find( p => {
city = p.city
setCity(p.city)
});
const response = await TimeApi.get(`/${city}.json`);
setTime(response.data);
}
Related
I have a Firestore collection, schemed as follows:
posts{
uid{
userPosts{
postID{
creation:
postText:
}
}
}
}
I want to display all of the posts, so I've made the corresponding queries and saved them in posts - an array of all the posts that I later iterate through.
The problem with the way I do it is that it keeps adding the same posts every render. So I've tried to set the array each time, but that way the code never passes through these posts && posts.length > 0 condition.
I'm really new to RN and JS in general, but what I was expecting is
Nothing to show here
at first, and then the list of posts.
The complete component:
import { Text, Pressable, FlatList, SafeAreaView } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import { db } from "../../../firebase";
import Post from "../../API/Post";
import { collection, getDocs } from "firebase/firestore";
const FeedScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPostData = async () => {
setPosts([]); // ---> Without this line the posts keeps adding each render
const q = collection(db, "posts");
const docSnap = await getDocs(q);
docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
tmpSnap.docs.map(async (element) => {
setPosts((prev) => {
prev.push(element.data());
return prev;
});
});
});
};
getPostData().catch(console.error);
return;
}, []);
return (
<SafeAreaView style={globalStyles.global}>
{posts && posts.length > 0 ? (
<FlatList
data={posts}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
keyExtractor={(item, index) => index.toString()}
/>
) : (
<Text>Nothing to show here</Text>
)}
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreatePost", { navigation });
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</SafeAreaView>
);
};
export default FeedScreen;
As said, I'm new to this so I'd love an explanation of what actually happens and how to do it properly.
I think the prev value of setPosts will always be [] since it does not immediately update if you call it. A standard way to do it is to call setPosts at the end of your function. Can you try this one?
useEffect(() => {
const getPostData = async () => {
const q = collection(db, "posts");
const docSnap = await getDocs(q);
const promises = docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
return tmpSnap.docs.map((element) => element.data());
});
const arrayOfPosts = await Promise.all(promises);
let newPosts = [];
arrayOfPosts.forEach((posts) => {
newPosts = [...newPosts, ...posts];
});
setPosts(newPosts);
};
getPostData().catch(console.error);
return;
}, []);
Good evening, I'm developing my first React Native Expo app for hobby purposes. It's an application that uses the mobile phone geolocation to track the user movements in order to organize a treasure hunt in the city.
But I'm having a problem: the watchPositionAsync function works very erratically. Sometimes the value of the location object is returned right away. Most of the time though, it doesn't get returned at all or even takes 10 minutes to do so.
This is the first version of the code, with only the function in question:
import React, {useState, useEffect} from 'react';
import * as Location from 'expo-location';
const Navigator = () => {
const [deviceLocation, setDeviceLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const getLocationAsync = async() => {
let loc = await Location.watchPositionAsync({
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 10000,
distanceInterval : 20
},
(newLocation) => {
setDeviceLocation(newLocation);
}
);
};
getLocationAsync()
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
})();
}, []);
let text = 'Waiting..';
if (errorMsg) {
text = errorMsg;
} else if (deviceLocation.location !== '') {
text = JSON.stringify(deviceLocation.location);
}
return (
<View>
<Text>{text}</Text>
</View>
)
}
This is the version with additional functions (such as calculating the distance from a predetermined point at each re-render of the watchPositionAsync function and setting a specific sentence to be communicated to the user):
import React, {useState, useEffect} from 'react';
import NavigatorUI from './NavigatorUI';
import * as Location from 'expo-location';
import { getDistance, findNearest } from 'geolib';
const treasureLocation = {
latitude: 39.2695552,
longitude: 8.4679172
}
const Navigator = () => {
const [deviceLocation, setDeviceLocation] = useState({
location : '',
treasureDist : '',
sentence: ''
});
const [errorMsg, setErrorMsg] = useState(null);
const getLocationAsync = async() => {
let loc = await Location.watchPositionAsync({
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 10000,
distanceInterval : 20
},
(newLocation) => {
setDeviceLocation({...deviceLocation, location: newLocation});
getDistfromTreasure();
switchDist();
console.warn(deviceLocation.sentence)
}
);
};
getLocationAsync()
const getDistfromTreasure = () => {
if(deviceLocation.location){
let {latitude: deviceLat, longitude: deviceLong} = deviceLocation.location.coords;
let {latitude: treasureLat, longitude: treasureLong} = treasureLocation;
let dist = getDistance({
latitude: deviceLat,
longitude: deviceLong
},
{
latitude: treasureLat,
longitude: treasureLong
}
);
setDeviceLocation({...deviceLocation, treasureDist: dist})
}
};
const switchDist = () =>{
if(deviceLocation.treasureDist){
let {treasureDist: val} = deviceLocation;
if(val > 1000){
setDeviceLocation({...deviceLocation, sentence: 'Example1'});
}
else if(val < 1000 && val > 500){
setDeviceLocation({...deviceLocation, sentence: 'Example2'});
}
else if(val < 500 && val > 100){
setDeviceLocation({...deviceLocation, sentence: 'Example3'});
}
else if(val < 100 && val > 50){
setDeviceLocation({...deviceLocation, sentence: 'Example4'});
}
else if(val < 50){
setDeviceLocation({...deviceLocation, sentence: 'Example5'});
}
}
}
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
})();
}, []);
let text = 'Waiting..';
if (errorMsg) {
text = errorMsg;
} else if (deviceLocation.location !== '') {
text = JSON.stringify(deviceLocation.location);
}
return (
<>
<View>
<Text>{text}</Text>
</View>
<View>
<Text>{sentence}</Text>
</View>
</>
)
}
export default Navigator
In both cases the screen remains with the word 'Waiting' for an infinite time.
Also, in the second case, I noticed that the re-renders happen much faster or much slower than I set with the timeInterval option. In any case with discontinuous speed.
Has anyone had the same problem and managed to fix it?
the code seems almost ok for me. I think that in the first code you posted there is an error in the last else if before the render. The location object given by expo contains a property coords, and you need to cehck if deviceLocation is defined before reading that.
else if (deviceLocation.location !== '') {
text = JSON.stringify(deviceLocation.location);}
->
else if (deviceLocation && deviceLocation.coords) {
text = JSON.stringify(deviceLocation.coords)};
I also played a little bit with that code and i would write it as:
const Navigator = () => {
console.log('render')
const [permissionResult, setPermissionResult] = useState(undefined);
const [deviceLocation, setDeviceLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const getPermissionAsync = async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
setPermissionResult(status)
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
}
const getLocationAsync = async() => {
await Location.watchPositionAsync({
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 10000,
distanceInterval : 20
},
(newLocation) => {
setDeviceLocation(newLocation);
}
);
};
useEffect(() => {
// If permission request is not done
if (permissionResult === undefined) {
getPermissionAsync();
}
}, [permissionResult]);
useEffect(()=>{
// If permission has been done and the result is available
if (permissionResult !== undefined) {
getLocationAsync()
}
}, [permissionResult])
let text = 'Waiting..';
if (errorMsg) {
console.log('errore')
text = errorMsg;
} else if (deviceLocation && deviceLocation.coords) {
text = JSON.stringify(deviceLocation.coords);
console.log(deviceLocation)
}
return (
<View>
<Text>{text}</Text>
</View>
)
}
I hope this will help, and that I understand the problem
The UserIndicator from <MapboxGL.UserLocation>, work and display fine on IOS, but with android, it's depends, it sometime work, sometime not, and i realize also, that my CameraRef.current.setCamera() is undefined when the UserIndicator doesn't display.
I tried to request with all the way i could, the location permissions like this :
React Native :
PermissionsAndroid.requestMultiple(
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION],
{
title: 'Give Location Permission',
message: 'App needs location permission to find your position.'
}
).then((res) => console.log(res))
MapboxGL :
if (Platform.OS == "android") {
var temp = await MapboxGL.requestAndroidLocationPermissions()
}
expo :
let { status } = await Location.requestPermissionsAsync();
all this request permissions work fine and have a output "granted" or granted : true
this is my map Component :
var Map = ({ navigation }) => {
const MapRef = React.useRef(null)
const CameraRef = React.useRef(null)
const LocationRef = React.useRef(null)
const { user, setUser } = React.useContext(UserContext)
const [data, setDATA] = React.useState([null])
const [reload, setReload] = React.useState(false)
const {location, setLocation} = React.useContext(LocationContext)
console.log(location)
var test = null;
React.useEffect(() => {
MapboxGL.setTelemetryEnabled(false);
MapboxGL.locationManager.start();
return () => {
MapboxGL.locationManager.stop();
}
// console.log(LocationRef.current)
}, [])
function handleClick() {
CameraRef.current.setCamera({
centerCoordinate: [location.coords.longitude, location.coords.latitude],
zoomLevel: 11,
animationDuration: 200,
})
}
function CenterCamera() {
if (CameraRef.current) {
CameraRef.current.setCamera({
centerCoordinate: [location.coords.longitude, location.coords.latitude],
zoomLevel: 11,
animationDuration: 2000,
})
}
}
function goTo(latitude, longitude) {
CameraRef.current.setCamera({
centerCoordinate: [longitude, latitude],
zoomLevel: 13,
animationDuration: 100,
})
}
function DisplayPings(data) {
if (data.data.length > 0) {
if (data.data[0].type_id == null) {
data.data[0].type_id = 1;
}
const val = searchInJson(data.data[0].type_id)
const features = setFeatures(val, data.data[0])
return (
<View key={data.data[0].id_activity_data}>
<MapboxGL.Images
images={{
FootBall: json[0].url,
}}
/>
<MapboxGL.ShapeSource hitbox={{ width: 20, height: 20 }} onPress={() => goTo(data.data[0].latitude, data.data[0].longitude)} id={(data.data[0].id_activity_data).toString()} shape={features}>
<MapboxGL.SymbolLayer id={(data.data[0].id_activity_data).toString()} style={{ iconImage: ['get', 'icon'] }} />
</MapboxGL.ShapeSource>
</View>
);
}
}
if (location && location.city != null && data.data) {
return (
<View style={styles.page}>
<MapboxGL.MapView onPress={() => console.log("test")} ref={(ref) => {
}
} style={styles.map} compassEnabled={false} zoomEnabled={true} >
<MapboxGL.UserLocation />
<MapboxGL.Camera ref={(ref) => {
CameraRef.current = ref
CenterCamera()
}} />
{data.data.map((data) => DisplayPings(data))}
</MapboxGL.MapView>
<ComponentsOnmap></ComponentsOnmap>
<TouchableOpacity style={styles.rondLocation} onPress={handleClick}>
<FontAwesome5 name="location-arrow" size={24} color="#434040" />
</TouchableOpacity>
<BottomSheet city={location.city} data = {data} setReload = {setReload} navigation={navigation}></BottomSheet>
</View>)
}
else
return (
<View style={styles.page}>
<MapboxGL.MapView ref={MapRef} compassEnabled={false} style={styles.map} zoomEnabled={true} >
<MapboxGL.UserLocation ref={LocationRef} />
</MapboxGL.MapView>
<ComponentsOnmap></ComponentsOnmap>
<BottomSheet city="No location" navigation={navigation}></BottomSheet>
</View>
)
}
export default Map
My condition location && location.city != null works fine, i tried without it, but the problem is still the same
My Location Context :
import React, { Component, createContext, useState, useContext } from "react";
import { Platform, PermissionsAndroid } from "react-native"
import * as Location from 'expo-location';
import MapboxGL from "#react-native-mapbox-gl/maps";
import { GetLocation } from "../API/GetLocation"
export const LocationContext = createContext();
MapboxGL.setAccessToken("Je l'ai caché bande de petit malin");
export default LocationProvider = ({ children }) => {
const [location, setLocation] = useState({ coords: [], city: null, permission: false })
const [city, SetCity] = React.useState(null)
const [coords, setCoords] = React.useState([])
React.useEffect(() => {
PermissionsAndroid.requestMultiple(
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION],
{
title: 'Give Location Permission',
message: 'App needs location permission to find your position.'
}
).then((res) => console.log(res))
GetLocation.then((res) => {
setLocation(res)
})
}, [])
return (
<LocationContext.Provider value={{ location, setLocation, city, SetCity, coords, setCoords }}>
{children}
</LocationContext.Provider>
)
}
My GetLocation Promise
import { useContext } from "react"
import { Platform } from "react-native"
import * as Location from 'expo-location';
import MapboxGL from "#react-native-mapbox-gl/maps";
export const GetLocation = new Promise(async (resolve, reject) => {
if (Platform.OS == "android") {
var temp = await MapboxGL.requestAndroidLocationPermissions()
}
let { status } = await Location.requestPermissionsAsync();
if (status == "granted")
Location.getCurrentPositionAsync().then((location) => {
console.log(location)
console.log("ca marche ap")
let longitude = location.coords.longitude
let latitude = location.coords.latitude
return ({ latitude, longitude })
}).then(async (coords) => Location.reverseGeocodeAsync(coords).then(async (adress) => {
resolve({ coords: coords, city: adress[0].city, granted: true })
})).catch((error) => console.log(reject(error)));
})
Im working on simply COVID-19 tracker and i have a problem.
Is there any option in Apollo for React to fetch graphql data once per button press?
Now i have TextInput and Button but when i fetch data once i can't type another country in input because i have immediately error.
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
setCountry(text);
}}
/>
<FinderButton
onPress={() => {
getCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;
Try using this
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
let inputValue = ‘’;
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
const onGetCountryStats = () => {
setCountry(inputValue);
getCountryStats();
}
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
inputValue = text;
}}
/>
<FinderButton
onPress={() => {
onGetCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;
I have the following React Native modules:
_localStorage.js
import AsyncStorage from '#react-native-community/async-storage';
const _storeData = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log(error);
}
}
const _retrieveData = async (key) => {
try {
await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
export {_storeData, _retrieveData};
AppHeader.js
import React from 'react';
import {Button} from 'react-native-paper';
import {_retrieveData, _storeData} from '../../utils/_localStorage'
const LoginButton = () => {
return (
<Button icon='login' color='yellow' onPress={() => navigation.navigate('Login')}>
Login
</Button>
)
}
const UserButton = (user) => {
return (
<Button color='yellow' onPress={() => console.log('Botón usuario presionado...')}>
text
</Button>
)
}
const AppHeader = ({navigation, route}) => {
const user = _retrieveData('user');
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
}
export default AppHeader;
I expect _retrieveData() to return the value of the key parameter, or null if it doesn't exits, but what I am getting in the console is this: {"_U": 0, "_V": 0, "_W": null, "_X": null}.
This is not how documentation of AsyncStorage indicates it works.
It's because you're not waiting for _retrieveData to finish. You're just setting user to the async function instead of waiting for its returned value.
Try something like this:
const AppHeader = ({navigation, route}) => {
const [user, setUser] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchUser();
}, [])
const fetchUser = async () => {
setIsLoading(true);
const userData = await _retrieveData('user');
setUser(userData);
setIsLoading(false);
}
if (isLoading) return <LoadingIndicator />
if (!!user) return <UserButton user={user} />
return <LoginButton />;
}
I've called fetchUser in the initial useEffect that gets called when the AppHeader component is first loaded. It sets a loading boolean to true and then requests the user data. When the userData is returned it sets it in state and sets loading to false.
You don't need the loading bit but I included it otherwise your app would show the login button while it's fetching the data. You'll have to create the LoadingIndicator component yourself.
_retrieveData is returning promise here. You need to await for that promise to resolve. Try writing it like this:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}
AppHeader.js
const AppHeader = ({navigation, route}) => {
_retrieveData('user').then((user)=>{
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
});
}
Read this for more clarity : https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
You're not returning anything from your _retrieveData function. Try writing it like so:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}