Where am I going wrong using hooks/state with geolocation in react native?

SuperHanz98 :

I have a component that is supposed to log the longitude/latitude/timestamp at regular intervals.

When the user presses START, the tracking should begin. When the user presser STOP, the tracking should stop.

To implement this, I have built the following (I am a complete beginner to react and JS so this could be entirely the wrong way to do this):

const Tracking = props => {

  const [currentLatitude, setCurrentLatitude] = useState(0);
  const [currentLongitude, setCurrentLongitude] = useState(0);
  const [currentTimestamp, setCurrentTimestamp] = useState(0);

  const [buttonTitle, setButtonTitle] = useState('Start');
  const [isTracking, setIsTracking] = useState(false);
  var getLocationInterval;


  function getLocation() {
    navigator.geolocation.getCurrentPosition(
      position => {
        setCurrentLongitude(position.coords.longitude);
        setCurrentLatitude(position.coords.latitude);
        setCurrentTimestamp(position.timestamp);
      },
      error => alert(error.message),
      { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
    );
    console.log(currentTimestamp, currentLatitude, currentLongitude);
  };

  function startStop() {
    if(!isTracking){
      //START
      setIsTracking(true);
      getLocationInterval = setInterval(getLocation, 500);
      setButtonTitle('Stop');
    }
    else{
      //STOP
      setIsTracking(false);
      clearInterval(getLocationInterval);
      setButtonTitle('Start');
    }
  };


  return (
    <View style={{width: '100%', height: '100%'}}>
      <MapView showsUserLocation style={{flex: 1}} />
      <MenuButton title = {buttonTitle} onPress={startStop}/>
    </View>
  );

}

Expected behaviour: once START is pressed, the button text changes to STOP. And in my console, I begin getting an output every 500ms with the latest lat/long/timestamp. When STOP is pressed, the button text changes to START and the outputs stop.

Actual behaviour: once START is pressed, the button text correctly changes to STOP, but only the initial states (0s) are repeatedly output. When I then press STOP, the next lat/long/timestamp starts being repeatedly output to the console. The 0s are also still being output because the interval doesn't seem to stop.

I'm guessing that I'm just using state completely wrong here. Please can someone help me out?

Zachary Haber :

So, there are a few issues in how you've set it up from a logic standpoint. I believe I've fixed all the issues I saw in your code below.

The reason why only 0 was being printed is because the currentLatitude/currentLongitude/currentTimestamp that was being referenced in the getLocation function had a closure around it once the interval was started where it wouldn't refresh to the new version of the function that was created on each re-render. To address that, I removed the references to those variables from the getLocation function in the changes I made.

The reason why there were still 0s being output is because the getLocationInterval was set to undefined in every render. You would've had to make that either a reference or a state variable in order to get that to stay between re-renders.

I moved the logic from the startStop function into a useEffect hook as that is the proper way to make an effect occur from within a hook component. It also allows all the logic to be combined in one straightforward location that can be extracted to a custom hook pretty easily if you need to use it in other locations.

const Tracking = props => {
const [currentLatitude, setCurrentLatitude] = useState(0);
const [currentLongitude, setCurrentLongitude] = useState(0);
const [currentTimestamp, setCurrentTimestamp] = useState(0);

const [isTracking, setIsTracking] = useState(false);

useEffect(() => {
    if (!isTracking) return;
    function getLocation() {
    navigator.geolocation.getCurrentPosition(
        position => {
        setCurrentLongitude(position.coords.longitude);
        setCurrentLatitude(position.coords.latitude);
        setCurrentTimestamp(position.timestamp);
        console.log(
            position.coords.longitude,
            position.coords.latitude,
            position.timestamp
        );
        },
        error => alert(error.message),
        { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
    );
    }
    let getLocationInterval = setInterval(getLocation, 500);
    return () => clearInterval(getLocationInterval);
}, [isTracking]);

return (
    <View style={{ width: '100%', height: '100%' }}>
    <MapView showsUserLocation style={{ flex: 1 }} />
    <MenuButton
        title={isTracking ? 'Stop' : 'Start'}
        onPress={() => {
        setIsTracking(!isTracking);
        }}
    />
    </View>
);
};

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=404616&siteId=1