I’m currently experiencing an issue where the values returned by the hook are unexpectedly undefined instead of the intended values. While logging within the useGpsLocation Hook indicates that the correct values are being set, the actual return values seem to be undefined. I think it has something to do with the way async works.
To elaborate further, I’m attempting to invoke the requestLocationPermissions function in another file, specifically in the UserProvider. Following this, I aim to assign the variables to the return values from useGpsLocation, so i can add the values in the database.
UseGpsLocation Hook:
import React, { useEffect, useState } from 'react';
import { Text, Button, Platform, PermissionsAndroid } from 'react-native';
import Geolocation from 'react-native-geolocation-service';
import h3 from 'h3-js';
import moment from 'moment-timezone';
type Location = {
lat: number;
lng: number;
};
const useGpsLocation = () => {
const [gpsLocation, setGpsLocation] = useState<Location | null>(null);
const [error, setError] = useState<string | null>(null);
const [showInputs, setShowInputs] = useState(false);
const [timeZone, setTimeZone] = useState('' as string);
const requestLocationPermissions = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Allow Location',
message:
'Do you accept that you are sharing your location ' +
'We dont use your specific location',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
await getLocation();
} else {
setError('Location permission denied');
}
} catch (err) {
console.warn(err);
}
};
const getTimeZone = (latitude: number, longitude: number) => {
const timezone = moment.tz.guess();
setTimeZone(timezone);
};
const getLocation = async () => {
Geolocation.getCurrentPosition(
position => {
const { latitude, longitude } = position.coords;
setShowInputs(true);
const latLngToCell = h3.latLngToCell(latitude, longitude, 6);
const location = h3.cellToLatLng(latLngToCell);
getTimeZone(latitude, longitude);
setGpsLocation({ lat: location[0], lng: location[1] });
},
error => console.log("The location could not be loaded because ", error.message),
{ enableHighAccuracy: false, timeout: 20000 }
);
};
return { gpsLocation, error, showInputs, requestLocationPermissions, getLocation, timeZone, };
};
export default useGpsLocation;
UserProvider file
export default function UserProvider({ children }: PropsWithChildren<unknown>) {
const { user, isAuthed, isLoading, refetch } = useUser();
const { mutateAsync: activateAccount } = useActivateAccount();
const { t } = useTranslation();
const navigation = useNavigation<NavigationProp<HomeStackParamList>>();
const { gpsLocation, timeZone, requestLocationPermissions } = useGpsLocation();
useDynamicLinks(async link => {
try {
const url = new URL(link.url);
const urlParams = new URLSearchParams(url.search);
const accountToken = urlParams.get("prod_token") || urlParams.get("test_token");
if (!accountToken) {
throw new Error();
}
await requestLocationPermissions();
const latitude = gpsLocation?.lat;
const longtitude = gpsLocation?.lng;
const tz_name = timeZone;
console.log(latitude + " " + longtitude + tz_name);
Burnt.alert({
title: t("providers.user_provider.activate.title"),
message: t("providers.user_provider.activate.message"),
preset: "spinner",
duration: 10,
});
// ActivateAccount and add values in database
const { authorization_token } = await activateAccount({ accountToken, latitude, longtitude, tz_name });
Burnt.dismissAllAlerts();
Burnt.alert({
title: t("providers.user_provider.activate_success.title"),
message: t("providers.user_provider.activate_success.message"),
preset: "done",
});
await setAuthToken(authorization_token);
await refetch();
navigation.navigate("HomeScreen");
} catch (err) {
Burnt.dismissAllAlerts();
const alertData = {
title: t("providers.user_provider.activate_error.title"),
message: `${t("providers.user_provider.activate_error.message")}${err ? `nn${err}` : ""}`,
};
if (Platform.OS === "android") {
Alert.alert(alertData.title, alertData.message);
} else {
Burnt.alert({
title: alertData.title,
message: alertData.message,
preset: "error",
duration: 4,
});
}
}
});
return <UserContext.Provider value={{ user, isAuthed, isLoading, refetch }}>{children}</UserContext.Provider>;
}
The values above here latitude, longtitude and tz_name give me undefined values.
Anyone that might know the fix?
2
Answers
The state is changing asynchronously. Use useEffect hook observe changes. Something like this:
The issue is more to do with a stale Javascript closure over the
gpsLocation
andtimeZone
values returned from theuseGpsLocation
hook in the callback function passed to theuseDynamicLinks
hook.Based on the way you’ve written the callback used by
useDynamicLinks
I suspect you don’t want, or can’t, refactor the logic to split the callingrequestLocationPermissions
function from the rest of the synchronous logic of the link callback handler. My suggestion here would be to rewrite theuseGpsLocation
hook a bit such thatrequestLocationPermissions
also returns thegpsLocation
andtimeZone
values the hook returns.Example Rewrite: