I am rendering a list of Trips
objects inside a FlatList
. So I have a screen named Network
where I have FlatList
which represents each of the trips. My render method:
return (
<View style={styles.viewStyle}>
<FlatList
numColumns={1}
horizontal={false}
data={trips}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<View key={index}>
<Trip trip={item} = />
</View>
)}
/>
</View>
);
Inside my Trip
component is the trip information. Trip's name AND trip's geo locations. From those geolocations I want to get the trip's city and country. To do so I call expo
's Location
API inside my useEffect
function, for each trip:
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
However, it seems that this function id being called only once for the very last trip, from all trips inside my FlatList
. This is how my Trip.js
component looks like:
import React, { useState, useEffect } from "react";
import { Text, TouchableOpacity } from "react-native";
import * as Location from "expo-location";
const Trip = ({ trip }) => {
const [city, setCity] = useState(null);
const [country, setCountry] = useState(null);
const { latitude, longitude } = trip;
console.log("trip name: ", trip.placeName);
console.log("latitude: ", latitude);
console.log("longitude: ", longitude);
if (!trip) {
return null;
}
useEffect(() => {
console.log("calling use effect from trip summary: ", trip.placeName);
async function fetchLocationName() {
console.log("calling async function");
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
console.log("response: ", response);
setCity(response[0].city);
setCountry(response[0].country);
}
fetchLocationName();
}, [trip.id]);
return (
<TouchableOpacity style={{ flexDirection: "row", flexWrap: "wrap" }}>
<Text>
<Text style={styles.textStyle}>{trip.placeName} </Text>
<Text style={styles.textStyle}>near </Text>
<Text style={styles.textStyleHighlithed}>{city}, </Text>
<Text style={styles.textStyleHighlithed}>{country} </Text>
</Text>
</TouchableOpacity>
);
};
export default Trip;
I put so many console.log
s because I wanted to be sure that I have trip.longitude
and trip.latitude
which, indeed, I have. What I see printed on the console:
latitude: 126.3936269
longitude: 59.3397108
latitude: 71.34165024
longitude: 129.7406225
calling use effect from trip summary: trip one
calling async function
calling use effect from trip summary: second trip
calling async function
response: Array [
Object {
"city": "some city",
"country": "some country",
...
},
]
And indeed on my screen I see only the very last trip's city and country being shown.
How to make sure that my useEffect
function is being called for every single trip, not just the last one?
CodePudding user response:
Your logs show that useEffect is being called twice:
calling use effect from trip summary: trip one
calling async function
calling use effect from trip summary: second trip
calling async function
So it's not the useEffect that's the problem. The issue is that you're never getting a return value from Location.reverseGeocodeAsync
for one of your calls.
Looking in the Expo docs for Location, you can see the following warning:
Note: Geocoding is resource consuming and has to be used reasonably. Creating too many requests at a time can result in an error, so they have to be managed properly. It's also discouraged to use geocoding while the app is in the background and its results won't be shown to the user immediately.
In the iOS code for expo-location, the following line gets printed if there are too many calls: Rate limit exceeded - too many requests
. If you're seeing that line, you need to make a way to space out these requests.
reverseGeocodeAsync
also takes an options argument that allows you to use Google's location service instead ({ useGoogleMaps: true }
).
So in summary, here are two things to try. You can rewrite your useEffect to explicitly catch errors in case they're not showing up (removed logs for brevity):
useEffect(() => {
async function fetchLocationName() {
try {
const response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
setCity(response[0].city);
setCountry(response[0].country);
} catch (error) {
console.error(error);
};
}
fetchLocationName();
}, [trip.id]);
And if you are seeing the rate limit error, you would need to build a request queue that spaces out the calls enough to avoid that.
Or you can try using Google's service, which would be the same except for the line that calls reverseGeocodeAsync
:
useEffect(() => {
async function fetchLocationName() {
try {
const response = await Location.reverseGeocodeAsync({
latitude,
longitude,
}, { useGoogleMaps: true });
...
CodePudding user response:
You can write your async function like this: try this
useEffect(async()=>{
console.log("calling use effect from trip summary: ", trip.placeName);
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
console.log("response: ", response);
setCity(response[0].city);
setCountry(response[0].country);
},[trip.id])
useEffect
runs after component gets rendered on the screen as an effect for the dependency you put in the array, DO ADD ALL THE CONSOLE LOGS OF THE TRIP COMPONENT
in the question.
console.log("trip name: ", trip.placeName);
-------------------------
console.log("calling use effect from trip summary: ", trip.placeName);
Even if you acheive the functionality you intend to, Calling an api from every item of flatlist is not a good approach as it will reduce performance. A better way would be to call the api for particular set of latitude and longitude on some user interaction event. that would be singular, synchronous and performant.