Search docs

Vue

An address autocomplete in Vue 3 with api.geolith.app: a composable with debouncing and request cancelling, and a single-file component.

By · Updated

A composable that fetches suggestions from /v1/autocomplete, and a component that uses it. Use a publishable key whose allowed origins include your site. Works in Vue 3 and Nuxt.

The composable

// useGeolithAutocomplete.ts
import { ref, watch, type Ref } from 'vue';

export interface Place {
  type: 'Feature';
  geometry: { type: 'Point'; coordinates: [number, number] };
  properties: { gid: string; label: string; postalcode?: string; locality?: string; country_code?: string };
}

export function useGeolithAutocomplete(text: Ref<string>, apiKey: string) {
  const places = ref<Place[]>([]);
  /** The label last picked; typing it back does not search again. */
  const picked = ref('');
  let timer: ReturnType<typeof setTimeout> | undefined;
  let controller: AbortController | undefined;

  watch(text, (value) => {
    clearTimeout(timer);
    controller?.abort();
    if (value.trim().length < 3 || value === picked.value) {
      places.value = [];
      return;
    }
    timer = setTimeout(async () => {
      controller = new AbortController();
      const url = new URL('https://api.geolith.app/v1/autocomplete');
      url.searchParams.set('text', value);
      url.searchParams.set('size', '5');
      url.searchParams.set('api_key', apiKey);
      try {
        const res = await fetch(url, { signal: controller.signal });
        if (res.ok) places.value = ((await res.json()) as { features: Place[] }).features;
      } catch (error) {
        if ((error as Error).name !== 'AbortError') throw error;
      }
    }, 200);
  });

  return { places, picked };
}

The component

<!-- AddressAutocomplete.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useGeolithAutocomplete, type Place } from './useGeolithAutocomplete';

const emit = defineEmits<{ select: [place: Place] }>();
const text = ref('');
const { places, picked } = useGeolithAutocomplete(text, import.meta.env.VITE_GEOLITH_KEY);

function pick(place: Place) {
  picked.value = place.properties.label;
  text.value = place.properties.label;
  places.value = [];
  emit('select', place);
}
</script>

<template>
  <div>
    <input v-model="text" type="search" placeholder="Start typing an address" autocomplete="off" />
    <ul role="listbox">
      <li v-for="place in places" :key="place.properties.gid" role="option" :aria-selected="false">
        <button type="button" @click="pick(place)">{{ place.properties.label }}</button>
      </li>
    </ul>
    <small>© OpenStreetMap contributors (ODbL), Overture Maps Foundation</small>
  </div>
</template>

Put the key in .env.local as VITE_GEOLITH_KEY=gc_pk_…. In Nuxt, read it from useRuntimeConfig().public instead of import.meta.env.