Créer une saisie semi-automatique d'adresse

Quelqu'un remplit une adresse de livraison. Vous voulez qu'il tape quatre caractères et choisisse le bon bâtiment, et vous voulez le résultat en parties que vous pouvez stocker plutôt qu'une seule chaîne à analyser plus tard.

résultat en direct
le code qui l’a produit
import { Unmap } from "@unmap/sdk";

const unmap = new Unmap({ key: "um_live_..." });

const results = await unmap.geocoder.autocomplete("rue sainte-cath", { limit: 5, lang: "fr" });

Le code ci-dessous est du TypeScript ordinaire. Pour le câblage React, Next.js, Vue ou Nuxt, partez des recettes par cadriciel, ou copiez le composant terminé avec npx shadcn add @unmap/geocoder.

Envoyez tout le champ, à chaque frappe

La saisie semi-automatique est faite pour cela. Tous les mots sauf le dernier doivent correspondre à un jeton entier, et le dernier est traité comme le préfixe en cours de frappe : la requête est donc tout le champ, pas le dernier mot.

const suggestions = await unmap.geocoder.autocomplete("1000 rue sainte-cath", {
  limit: 5,
  lang: "fr",
});

Trois caractères ou moins répondent avec des lieux seulement : villes, provinces et points d'intérêt. Les adresses et les rues arrivent au quatrième caractère, délibérément, parce qu'à trois caractères presque personne ne tape un bâtiment.

Ne courez pas contre vous-même

Deux choses tournent mal dans toute saisie semi-automatique écrite à la main, et les deux produisent le même symptôme : la liste revient par à-coups à une réponse plus ancienne pendant la frappe.

Temporisez, pour ne pas interroger à chaque touche. Entre 120 et 200 ms suffisent à couper la plupart des requêtes sans que le champ paraisse lent.

Jetez les réponses dont vous ne voulez plus. Une requête pour mont peut revenir après une requête pour montreal, et l'afficher met la mauvaise liste à l'écran. Annulez avec un AbortSignal, et gardez aussi un compteur de génération, car une annulation qui perd la course doit quand même être ignorée.

import type { GeocodeResult } from "@unmap/geocoding";
 
function makeSuggester(render: (results: GeocodeResult[]) => void) {
  let generation = 0;
  let inFlight: AbortController | undefined;
 
  return async function suggest(query: string): Promise<void> {
    inFlight?.abort();
    const controller = new AbortController();
    inFlight = controller;
    const mine = ++generation;
 
    const results = await unmap.geocoder.autocomplete(query, {
      limit: 5,
      signal: controller.signal,
    });
 
    // Une réponse tardive pour une frappe antérieure est jetée plutôt qu'affichée.
    if (mine !== generation) return;
    render(results);
  };
}

Annuler ne défacture pas la requête. L'annulation vous évite d'attendre la réponse; le serveur a peut-être déjà fait le travail. C'est la temporisation qui réduit le nombre d'appels. L'annulation ne fait que garder la liste correcte.

Prenez les parties, pas le libellé

La raison d'utiliser un géocodeur ici plutôt qu'un champ de texte ordinaire, c'est ce qui revient quand quelqu'un choisit une suggestion. address porte les composants; lng et lat sont le point.

import type { GeocodeResult } from "@unmap/geocoding";
 
function toFormFields(choice: GeocodeResult) {
  return {
    line1: [choice.address?.housenumber, choice.address?.street].filter(Boolean).join(" "),
    unit: choice.address?.unit ?? "",
    city: choice.address?.city ?? "",
    region: choice.address?.region ?? "",
    postalCode: choice.address?.postcode ?? "",
    lng: choice.lng,
    lat: choice.lat,
  };
}

Tous les champs sauf lng et lat peuvent être absents, et la raison honnête tient aux données plutôt qu'à l'API : toutes les sources ne publient pas d'unité ni de code postal, et un résultat de localité n'a aucune rue. Stockez ce qui est revenu; ne reconstituez pas les parties en découpant name.

Gardez le clavier fonctionnel

Une saisie semi-automatique qui ne fonctionne qu'à la souris est cassée pour beaucoup de gens et échoue à toute revue d'accessibilité. En bref : le champ garde le focus et porte aria-activedescendant; la liste est un listbox et chaque ligne une option; les flèches haut et bas déplacent l'option active; Entrée la choisit; Échap ferme la liste sans choisir. Le composant du registre (npx shadcn add @unmap/geocoder) fait déjà tout cela, ce qui est l'argument pour le copier plutôt que le réécrire.

Orientez la recherche vers l'utilisateur

Si vous savez à peu près où il se trouve, dites-le. focus rapproche le classement d'un point sans rien exclure : un utilisateur de Halifax qui tape main st voit d'abord celle de Halifax et peut toujours atteindre celle de Vancouver.

const nearMe = await unmap.geocoder.autocomplete("main st", {
  focus: [-63.5752, 44.6488],
  limit: 5,
});

Utilisez plutôt bbox quand vous voulez vraiment dire « rien en dehors de cette zone ». La différence n'est pas cosmétique : focus privilégie, bbox exclut.

Là où cette approche se trompe

  • Les unités sont rarement dans les données. Une requête qui en nomme une revient habituellement sans, et le résultat indique unit_not_verified dans match_reasons. Collectez l'unité dans votre propre champ.
  • Les routes rurales ne sont pas géocodables. RR 2 est un circuit de facteur, pas un lieu. L'API retire les termes de distribution et répond avec la communauté; voir couverture.
  • La saisie semi-automatique ne corrige pas les fautes de frappe. Il n'y a pas de passe floue ici, volontairement : elle coûterait de la latence à chaque touche. search réessaie avec une passe tolérante aux fautes quand la passe exacte revient vide, donc un état « aucune suggestion » peut s'y replier.

Voir aussi