Search code examples
reactjsreact-nativegeolocationreact-native-navigation

React Native GeoLocation.watchPosition() using Initial value of state in success callback


I am making a "Treasure hunt" trails app. A user can go to a location and when they reach that location an action will happen. To monitor the users location I am using Gelocation.watchposition() from "react-native-geolocation-service".

The point of interest (trailLocation) is got from our API and I set the "trailLocation" state with this, it updates correctly and all looks good. (initialTrailLocation passed from previous screen)

    const [trailLocation, setTrailLocation] = useState(initialTrailLocation);

/**
     * Function to get the next trail location
     */
    let getNextLocation = async () => {
        setIsLoading(true);
        const credentials = await Keychain.getGenericPassword();
        await axios.get(BACKEND_URL + 'api/v1/trails/' + trail.id + '/user/' + credentials.username + '/next-location?include=trail_location_ar_object.ar_object.ar_object_resources', {
                headers: {
                    Authorization: 'Bearer ' + credentials.password,
                },
            },
        )
        .then(response => {
            setTrailLocation(response.data.data.trail_location);
            setIsLoading(false);
            setUserWithinRange(false);
        })
        .catch(error => {
            console.log(error);
        });
    };

    /**
     * Function called when the position changes
     */
    function newPositionHandler(data) {
        console.log("new position handler");
        let radius = 1000;
        let ky = 40000 / 360;
        console.log(trailLocation);
        let kx = Math.cos((Math.PI * trailLocation.lat) / 180.0) * ky;
        let dx = Math.abs(trailLocation.lon - data.coords.longitude) * kx;
        let dy = Math.abs(trailLocation.lat - data.coords.latitude) * ky;

        setDistance(Math.sqrt(dx * dx + dy * dy));
        console.log(Math.sqrt(dx * dx + dy * dy));
        console.log('-------------------');
        if(Math.sqrt(dx * dx + dy * dy) <= radius / 1000) {
            setUserWithinRange(true);
        } else {
            setUserWithinRange(false)
        }
    };

    /** Function called to initialise the watch position functionality */
    async function watchPosition() {
        console.log("watch position");
        Geolocation.watchPosition((data) => newPositionHandler(data), (error) => console.log(error),{
            enableHighAccuracy: false,
            timeout: 10000,
            maximumAge: 1000,
            distanceFilter: 5,
            },
        );
    };

However, when the success function from watchposition is triggered, it uses the original value of the "trailLocation" state and hence calculates the wrong distance between the user location and new trailLocation point. I can't understand why this is as all other functions use the correct state value. I log the values out and I can clearly see it using the initial state, but all other actions use the current state and the new trailLocation parameters are displayed on the screen.

Any help would be greatly appreciated. I can provide more details, it's my first question so cut me some slack ;)

Thanks


Solution

  • The data in your function is outdated - what's often referred to as a "stale closure". When you write Geolocation.watchPosition((data) => newPositionHandler(data), ..., the function is created with the state that exists at the time. When the function runs, this data has become outdated.

    You can read more about solutions to this problem in this related question: How To Solve The React Hook Closure Issue?