Ajouter une recherche de lieux à React
Une boîte de recherche au-dessus d'une API de géocodage demande plus de travail qu'il n'y paraît : appliquer un anti-rebond à la saisie, annuler la requête déjà en vol, garder la liste navigable au clavier, et déplacer la carte quand un choix est fait. Le composant copié fait déjà tout cela. Le hook vous donne les appels et vous laisse l'interface.
Installation
npx @unmap/cli add geocodernpm i @unmap/reactL'élément du registre est un Combobox construit à partir de vos propres primitives shadcn : il hérite donc de votre anneau de focus, de votre rayon et de votre palette au lieu d'arriver avec des idées arrêtées.
Le composant
Le <Geocoder> copié se place dans <Map>, et c'est ainsi qu'il trouve la carte à survoler : tous
deux lisent le même map-context qu'add a installé à côté d'eux.
import { Map } from "@/components/ui/map";
import { Geocoder } from "@/components/ui/geocoder";
export default function App() {
const apiKey = import.meta.env.VITE_UNMAP_KEY as string;
return (
<div className="h-dvh">
<Map apiKey={apiKey} center={[-114.07, 51.05]} zoom={11} className="h-full">
<div className="absolute left-3 top-3 z-10 w-72">
<Geocoder apiKey={apiKey} placeholder="Rechercher un lieu au Canada" />
</div>
</Map>
</div>
);
}import { useState } from "react";
import { useGeocoder } from "@unmap/react";
import type { GeocodeResult } from "@unmap/sdk";
export default function Search() {
const { autocomplete, results, loading } = useGeocoder({
apiKey: import.meta.env.VITE_UNMAP_KEY as string,
});
const [chosen, setChosen] = useState<GeocodeResult | null>(null);
return (
<div>
<input
onChange={(event) => void autocomplete(event.target.value, { limit: 5 })}
placeholder="Rechercher un lieu au Canada"
/>
{loading && <span>Recherche…</span>}
<ul>
{results.map((result) => (
<li key={result.id}>
<button onClick={() => setChosen(result)}>{result.name}</button>
</li>
))}
</ul>
{chosen && <p>{chosen.lng}, {chosen.lat}</p>}
</div>
);
}L'anti-rebond vous revient sur la voie du paquet
L'extrait ci-dessus appelle l'API à chaque frappe, ce qui est faux d'une manière qu'il vaut mieux dire explicitement : chaque requête d'autocomplétion est un appel facturé. Taper « Calgary » sans limitation fait sept appels pour répondre à une seule question. Le composant copié applique un anti-rebond de 200 ms ; le hook ne le fait volontairement pas, car un hook qui déciderait de votre rythme serait un hook qu'il faudrait combattre.
import { useEffect, useRef, useState } from "react";
import { useGeocoder } from "@unmap/react";
export default function DebouncedSearch() {
const { autocomplete, results } = useGeocoder({
apiKey: import.meta.env.VITE_UNMAP_KEY as string,
});
const [query, setQuery] = useState("");
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
useEffect(() => {
if (query.length < 3) return;
clearTimeout(timer.current);
timer.current = setTimeout(() => void autocomplete(query, { limit: 5 }), 200);
return () => clearTimeout(timer.current);
}, [query, autocomplete]);
return (
<div>
<input value={query} onChange={(event) => setQuery(event.target.value)} />
<ul>
{results.map((result) => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}Le seuil de trois caractères compte autant que le délai. Les requêtes d'une ou deux lettres correspondent à presque tout et ne valent rien pour la personne qui tape.
Ce qui revient
Chaque résultat porte name, lng, lat, un layer qui dit de quel type d'objet il s'agit, et
une décomposition address quand la ligne en a une. Les lignes bilingues portent aussi names :
une rue montréalaise peut donc afficher les deux formes sans seconde requête, et lang décide
laquelle name contient.
Les résultats sont classés, pas filtrés. La liste est ce que le corpus juge le plus proche du texte, et pour une requête courte le premier résultat peut être une ville alors que l'utilisateur pensait à une rue qui s'y trouve.
Problèmes courants
Chaque frappe est une requête. Voir ci-dessus. C'est l'erreur la plus coûteuse disponible sur cette page.
La carte ne bouge pas sur la voie du registre. <Geocoder> est hors de <Map> : il n'a donc
aucune carte à survoler. Il doit en être un enfant.
Les résultats semblent faux pour une rue. Ajoutez le reste de l'adresse. « 17 Ave SW » seul est ambigu à l'échelle du pays ; « 17 Ave SW Calgary » ne l'est pas.
Pour aller plus loin
- Autocomplétion pour la version indépendante de tout framework
- API de géocodage pour les quatre points de terminaison et le classement
- Ajouter une carte à React pour la carte sur laquelle cela repose