Search code examples
javascripttypescriptidetypechecking

Autocomplete disabled when using typescript union type checking


I'm trying to get the typescript autocomplete to work with a function like this :

interface WeatherData {
  coord: { lon: number; lat: number };
}

const fetchWeatherData = async (
  location: string
): Promise<WeatherData | Error> => {
  try {
    const res = await axios.get(
      `/weathr?q=${location}&APPID=${process.env.API_KEY}`
    );
    return res.data;
  } catch (e) {
    return new Error("Couldn't fetch weather data");
  }
};

It will return the data If network request is successful otherwise an error with a custom message.

But autocomplete is completely disabled this way and I can't access any fields I defined in WeatherData interface :

autocomplete

How can I use the autocomplete feature with typescript union type checking ?


Solution

  • The type WeatherData | Error is not guaranteed to have any specific property, so you can't destructure it.

    interface WeatherData {
      coord: { lon: number; lat: number };
    }
    
    // Use `declare` to just work with the type of this function.
    declare function fetchWeatherData(): WeatherData | Error
    
    async function go() {
      const { coord } = await fetchWeatherData()
    }
    

    This gives the type error:

    Property 'coord' does not exist on type 'WeatherData | Error'.(2339)

    This is because coord is not a property that exists on Error, so if an object of type Error is returned, then you are accessing an invalid property.

    See PLayground


    The simple fix is narrow the type to make sure it's not an error before you access any non-error properties:

    interface WeatherData {
      coord: { lon: number; lat: number };
    }
    
    // Use `declare` to just work with the type of this function.
    declare function fetchWeatherData(): WeatherData | Error
    
    async function go() {
      const result = await fetchWeatherData()
      if (result instanceof Error) {
        console.error('Failed to fetch weather', result)
      } else {
        console.log('geo location', result.coord.lat, result.coord.lon) // fine
      }
    }
    

    See Playground


    Although, perhaps what you meant to do was to throw an Error, which interrupts the normal control flow. This would allow you to only return a WeatherData since the code that handles the return value will be skipped if the error is raised.

    interface WeatherData {
      coord: { lon: number; lat: number };
    }
    
    function fetchWeatherData(): WeatherData {
      if (Math.random() > 0.9) throw new Error('Failed to fetch weather')
      return { coord: { lat: 123, lon: 45 }}
    }
    
    async function go() {
      const { coord } = await fetchWeatherData() // fine
    }
    

    See Playground