I can only get the API parameters, after I press CTRL+S or update my file

Viewed 23

I'm having a problem, I made an API call to find out the city of the user who accessed my application.

So far so good, I can show on the screen the city the user is in, but only after I press the CTRL + S keys (save my file) then the API GET works. Same in the example below

When loading the application:

enter image description here

After I save the application in my code editor:

enter image description here

Can you tell me how I can make the location appear immediately without me having to save my code?

Below is my code for this component

Location.tsx

import axios from "axios";
import { useEffect, useState } from "react"

export function Location(){

    const API_endpoint = `https://api.openweathermap.org/data/2.5/weather?`
    const API_key = `e3b1d84b18bc5ef312403e0caf94b698`

    const [latitude, setLatitude] = useState<number>(0);
    const [longitude, setLongitude] = useState<number>(0);
    const [cityName, setCityName] = useState<string>('')
    const [isFetching,setIsFetching] = useState<boolean>(true);
    const [error, setError] = useState<Error | null>(null)
    

    useEffect(() => {
    navigator.geolocation.getCurrentPosition((position) => {
        setLatitude(position.coords.latitude)
        setLongitude(position.coords.longitude)
    })     

    let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
    
    axios.get(finalApiEndPoint)
    .then((response) =>{
        setCityName(response.data.name)
        console.log(response.data.name)
    })
    .catch(err => {
        setError(err)
      })
      .finally(() => {
        setIsFetching(false)
      })
    }, [])
    


    return(
        <>
        <div>
            <h1>
                <>
                Your Location: {cityName}
                </>
            </h1>
        </div>
        </>
    )
}
1 Answers

The issue is a combination of:

  1. Asynchronous code trying to use a result before it is received
  2. Synchronous usage of React state after change, before the change takes effect (next render)

1. Using result from asynchronous code

In the useEffect, the call to navigator.geolocation.getCurrentPosition properly uses its result within a callback, because it will take some time before the gelocation is known.

So anything that depends on this data should also be executed only after that data is received, i.e. typically within said callback. Or through the states where you store the geolocation results for decoupling.

2. React state changes on next render

However, when setting a new React state value, that state still holds the previous value for the rest of this render pass: any synchronous code after setLatitude will still see the old value of latitude, until next render (it is a const, after all).

So either use directly position.coords.latitude (which shows that it must be executed within the callback), or push the decoupling further by using a separate useEffect, so that the API call is executed only once the states have actually changed their value.

const [latitude, setLatitude] = useState<number>(0);
const [longitude, setLongitude] = useState<number>(0);
const [cityName, setCityName] = useState<string>('')
    
useEffect(() => { 
    navigator.geolocation.getCurrentPosition((position) => {
        setLatitude(position.coords.latitude) 
        setLongitude(position.coords.longitude)
    })
}, []) // Get geolocation only once

useEffect(() => {
    let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
    
    axios.get(finalApiEndPoint)
    .then((response) =>{
        setCityName(response.data.name)
        console.log(response.data.name)
    })
}, [latitude, longitude]) // Call API whenever lat or long change

The component suddenly displays the expected state value after saving your file, because it triggers a hot replacement. The latter preserves the state (hence the latitude and longitude of first geolocation), but the new component is re-mounted, which triggers a new execution of the useEffect, where the API call is re-made, using the preserved (now correct) states.

Related