Issue
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 ? `\n\n${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?
Solution
The issue is more to do with a stale Javascript closure over the gpsLocation
and timeZone
values returned from the useGpsLocation
hook in the callback function passed to the useDynamicLinks
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 calling requestLocationPermissions
function from the rest of the synchronous logic of the link callback handler. My suggestion here would be to rewrite the useGpsLocation
hook a bit such that requestLocationPermissions
also returns the gpsLocation
and timeZone
values the hook returns.
Example Rewrite:
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;
};
// Convert from callback syntax to Promise syntax
const getCurrentPosition = options => new Promise(
(resolve, reject) => Geolocation.getCurrentPosition(resolve, reject, options)
);
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 getTimeZone = (latitude: number, longitude: number) => {
return moment.tz.guess();
};
const getLocation = async () => {
try {
const position = await getCurrentPosition({
enableHighAccuracy: false,
timeout: 20000
});
const { latitude, longitude } = position.coords;
const latLngToCell = h3.latLngToCell(latitude, longitude, 6);
const [lat, lng] = h3.cellToLatLng(latLngToCell);
const gpsLocation = { lat, lng };
const timeZone = getTimeZone(latitude, longitude);
setShowInputs(true);
setTimeZone(timezone);
setGpsLocation(gpsLocation);
return { gpsLocation, timeZone };
} catch(error) {
console.log("The location could not be loaded because ", error.message);
}
};
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) {
return getLocation();
} else {
setError('Location permission denied');
throw new Error('Location permission denied');
}
} catch (err) {
console.warn(err);
throw err; // rethrow
}
};
return {
gpsLocation,
error,
showInputs,
requestLocationPermissions,
getLocation,
timeZone
};
};
export default useGpsLocation;
const {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 the returned values here
const { gpsLocation, timeZone } = 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 ? `\n\n${err}` : ""
}`,
};
if (Platform.OS === "android") {
Alert.alert(alertData.title, alertData.message);
} else {
Burnt.alert({
title: alertData.title,
message: alertData.message,
preset: "error",
duration: 4,
});
}
}
});
Answered By - Drew Reese
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.