skip to Main Content

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


  1. The state is changing asynchronously. Use useEffect hook observe changes. Something like this:

    useEffect(() => {
      const latitude = gpsLocation?.lat;
      const longtitude = gpsLocation?.lng;
      const tz_name = timeZone;
    
      console.log(latitude + " " + longtitude + tz_name);
    ...
    }, [gpsLocation, timeZone]);
    
    Login or Signup to reply.
  2. 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 ? `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,
          });
        }
      }
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search