Skip to main content
Автодополнение адреса в React-чекауте: как сократить отказы
Tutorials

Автодополнение адреса в React-чекауте: как сократить отказы

Добавьте автодополнение адреса в чекаут на React с MapAtlas Geocoding API. Меньше брошенных корзин, быстрее ввод на мобильных и проверка адреса прямо при вводе.

Brent van der Heiden10 min read
#address autocomplete#geocoding api#checkout conversion#address validation#javascript autocomplete

Поле адреса при оформлении заказа это место, где мобильная конверсия теряется чаще всего. Покупатель открывает страницу товара, кладёт его в корзину, переходит к оформлению, и тут ему нужно набрать полный адрес на экранной клавиатуре шестидюймового телефона. Если он ошибётся в индексе, запутается с форматом номера дома или просто бросит заказ, вы потеряете продажу, которая уже почти состоялась.

Автодополнение адреса решает эту проблему. Вместо 25 и более символов в нескольких полях покупатель набирает пару букв и делает одно касание. Он начинает вводить название улицы, через полсекунды видит подходящую подсказку, нажимает на неё, и весь адрес (улица, номер дома, город, индекс, страна) заполняется сам и без ошибок. Доставок, сорванных из-за опечаток, становится меньше. Брошенных заказов тоже. А главное, к каждому заказу прикреплены проверенные координаты, которые ваши системы логистики и маршрутизации используют напрямую.

По оценке Baymard Institute, продуманное оформление заказа может поднять конверсию на 35,26% для среднего крупного интернет-магазина. Тот же бенчмарк показал, что у 55% сайтов до сих пор нет полностью автоматического поиска адреса, поэтому с поля адреса проще всего начать. Сильнее всего разницу чувствуют покупатели с телефонов: печатать на телефонной клавиатуре медленно, и ошибок там больше.

В этом руководстве мы соберём полный React-компонент автодополнения адреса на MapAtlas Geocoding API: с задержкой запросов, навигацией с клавиатуры, поддержкой форматов адресов ЕС и подключением к форме. Весь компонент занимает около 90 строк.

Почему ошибки в адресе снижают конверсию

Сорванная доставка обходится дорого со всех сторон: перевозчик берёт плату за повторную доставку, поддержка разбирает жалобу, а покупатель меньше доверяет вашему магазину. В большом исследовании оформления заказа Baymard Institute 9% вручную введённых адресов доставки содержали опечатки или ошибки в написании.

Причины предсказуемы:

  • Ввод с телефонной клавиатуры даёт больше опечаток, чем с компьютера. Автозамена часто портит названия улиц и городов.
  • Форматы индексов в разных странах разные. Покупатель из Германии вводит пятизначный индекс в поле, которое ждёт британский формат (AN NAA), и получает ошибку проверки.
  • Порядок улицы и номера дома в странах ЕС разный. В Германии и Нидерландах номер дома идёт после названия улицы, во Франции перед ним. Формы ручного ввода редко подсказывают, как правильно.
  • Для квартир и этажей нет единого формата. Люди пишут их так, как им удобно, и часто не так, как ждёт ваш перевозчик.

Автодополнение снимает большую часть этих проблем: оно возвращает уже проверенный адрес в виде структурированного объекта. Покупатель выбирает нужный вариант, а форма получает адрес в правильном формате.

Эндпоинт автодополнения MapAtlas Geocoding API

Эндпоинт для подсказок автодополнения:

GET https://gateway.mapmetrics-atlas.net/autocomplete/?text={query}&token={YOUR_API_KEY}

Необязательные параметры, полезные для интернет-магазинов в ЕС:

ПараметрТипОписание
textstringЧасть адреса, которую ввёл пользователь
focus.point.lonnumberДолгота пользователя (ближайшие результаты выше)
focus.point.latnumberШирота пользователя (ближайшие результаты выше)
boundary.countrystringКод страны ISO 3166-1 alpha-2 (например, DE, FR, NL)
layersstringФильтр по типу результата: address, street, locality
sizenumberЧисло результатов (по умолчанию 10, максимум 20)

Типичный ответ:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [4.9041, 52.3676] },
      "properties": {
        "id": "address:node/1234567",
        "label": "Damrak 1, 1012 LG Amsterdam, Netherlands",
        "name": "Damrak 1",
        "street": "Damrak",
        "housenumber": "1",
        "postalcode": "1012 LG",
        "locality": "Amsterdam",
        "region": "North Holland",
        "country": "Netherlands",
        "country_code": "NL",
        "confidence": 0.98
      }
    }
  ]
}

Каждый результат приходит как объект GeoJSON с разобранными компонентами адреса. Форма получает чистые проверенные данные: их можно сразу разложить по полям или сохранить одним объектом вместе с координатами для планирования маршрутов и доставки.

Хук автодополнения адреса для React

Сначала вынесите работу с API в отдельный хук. Так компонент останется простым, а хук можно будет тестировать отдельно.

// hooks/useAddressAutocomplete.js
import { useState, useEffect, useRef } from 'react';

const API_BASE = 'https://gateway.mapmetrics-atlas.net/autocomplete/';
const API_KEY  = process.env.NEXT_PUBLIC_MAPATLAS_KEY;
const DEBOUNCE_MS = 300;
const MIN_CHARS   = 3;

export function useAddressAutocomplete(countryCode = null) {
  const [query,       setQuery]       = useState('');
  const [suggestions, setSuggestions] = useState([]);
  const [loading,     setLoading]     = useState(false);
  const [error,       setError]       = useState(null);
  const debounceTimer = useRef(null);

  useEffect(() => {
    if (query.length < MIN_CHARS) {
      setSuggestions([]);
      return;
    }

    clearTimeout(debounceTimer.current);

    debounceTimer.current = setTimeout(async () => {
      setLoading(true);
      setError(null);

      try {
        const url = new URL(API_BASE);
        url.searchParams.set('text', query);
        url.searchParams.set('token', API_KEY);
        url.searchParams.set('size', '6');
        url.searchParams.set('layers', 'address');
        if (countryCode) {
          url.searchParams.set('boundary.country', countryCode);
        }

        const res  = await fetch(url.toString());
        if (!res.ok) throw new Error(`API error: ${res.status}`);

        const data = await res.json();
        setSuggestions(data.features ?? []);
      } catch (err) {
        setError(err.message);
        setSuggestions([]);
      } finally {
        setLoading(false);
      }
    }, DEBOUNCE_MS);

    return () => clearTimeout(debounceTimer.current);
  }, [query, countryCode]);

  return { query, setQuery, suggestions, loading, error };
}

Таймер срабатывает, только когда пользователь не печатает 300 мс. Проверка MIN_CHARS не даёт отправлять запросы на 1-2 символа, где результаты слишком общие и бесполезны. Обе меры нужны, чтобы число запросов к API (и расходы) соответствовало тому, что пользователь действительно ищет.

Компонент автодополнения

// components/AddressAutocomplete.jsx
import { useState, useRef } from 'react';
import { useAddressAutocomplete } from '../hooks/useAddressAutocomplete';

export function AddressAutocomplete({ onSelect, countryCode, placeholder }) {
  const { query, setQuery, suggestions, loading } = useAddressAutocomplete(countryCode);
  const [open,        setOpen]    = useState(false);
  const [highlighted, setHighlighted] = useState(-1);
  const inputRef = useRef(null);

  function handleSelect(feature) {
    const p = feature.properties;
    setQuery(p.label);
    setOpen(false);
    setHighlighted(-1);
    onSelect({
      label:       p.label,
      street:      p.street      ?? '',
      housenumber: p.housenumber ?? '',
      postalcode:  p.postalcode  ?? '',
      locality:    p.locality    ?? '',
      region:      p.region      ?? '',
      country:     p.country     ?? '',
      country_code: p.country_code ?? '',
      coordinates: feature.geometry.coordinates, // [lng, lat]
    });
  }

  function handleKeyDown(e) {
    if (!open || suggestions.length === 0) return;
    if (e.key === 'ArrowDown')  setHighlighted(h => Math.min(h + 1, suggestions.length - 1));
    if (e.key === 'ArrowUp')    setHighlighted(h => Math.max(h - 1, 0));
    if (e.key === 'Enter' && highlighted >= 0) handleSelect(suggestions[highlighted]);
    if (e.key === 'Escape')     setOpen(false);
  }

  return (
    <div style={{ position: 'relative' }}>
      <input
        ref={inputRef}
        type="text"
        value={query}
        placeholder={placeholder ?? 'Start typing your address...'}
        onChange={e => { setQuery(e.target.value); setOpen(true); setHighlighted(-1); }}
        onKeyDown={handleKeyDown}
        onBlur={() => setTimeout(() => setOpen(false), 150)}
        style={{ width: '100%', padding: '10px 12px', fontSize: 16, borderRadius: 6, border: '1px solid #ccc' }}
        autoComplete="off"
        aria-autocomplete="list"
        aria-haspopup="listbox"
        aria-expanded={open && suggestions.length > 0}
      />

      {loading && (
        <span style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', fontSize: 12, color: '#888' }}>
          Searching…
        </span>
      )}

      {open && suggestions.length > 0 && (
        <ul
          role="listbox"
          style={{
            position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 999,
            background: '#fff', border: '1px solid #ccc', borderTop: 'none',
            borderRadius: '0 0 6px 6px', listStyle: 'none', margin: 0, padding: 0,
            boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
          }}
        >
          {suggestions.map((feature, i) => (
            <li
              key={feature.properties.id}
              role="option"
              aria-selected={i === highlighted}
              onMouseDown={() => handleSelect(feature)}
              onMouseEnter={() => setHighlighted(i)}
              style={{
                padding: '10px 12px',
                cursor: 'pointer',
                fontSize: 14,
                background: i === highlighted ? '#f0f7e6' : '#fff',
                borderBottom: i < suggestions.length - 1 ? '1px solid #f0f0f0' : 'none',
              }}
            >
              {feature.properties.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Компонент поддерживает полную навигацию с клавиатуры (стрелки, Enter, Escape) и атрибуты ARIA для экранных дикторов. Закрытие списка при потере фокуса отложено на 150 мс, чтобы клик мышью по подсказке успел сработать.

Подключение к форме оформления заказа

// pages/checkout.jsx
import { useState } from 'react';
import { AddressAutocomplete } from '../components/AddressAutocomplete';

export default function CheckoutPage() {
  const [address, setAddress] = useState({
    street: '', housenumber: '', postalcode: '',
    locality: '', country: '', coordinates: null,
  });

  function handleAddressSelect(selected) {
    setAddress(selected);
    // Coordinates are available for routing/delivery estimation
    console.log('Delivery coordinates:', selected.coordinates);
  }

  return (
    <form>
      <h2>Delivery address</h2>

      <AddressAutocomplete
        onSelect={handleAddressSelect}
        countryCode="NL"  // Restrict to Netherlands, remove for EU-wide
        placeholder="Start typing your street address..."
      />

      {/* Show structured fields after selection, allow manual edits */}
      {address.street && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, marginTop: 12 }}>
          <input value={address.street}      onChange={e => setAddress(a => ({ ...a, street: e.target.value }))}      placeholder="Street" />
          <input value={address.housenumber} onChange={e => setAddress(a => ({ ...a, housenumber: e.target.value }))} placeholder="No." style={{ width: 80 }} />
          <input value={address.postalcode}  onChange={e => setAddress(a => ({ ...a, postalcode: e.target.value }))}  placeholder="Postal code" />
          <input value={address.locality}    onChange={e => setAddress(a => ({ ...a, locality: e.target.value }))}    placeholder="City" />
        </div>
      )}

      <button type="submit" style={{ marginTop: 16 }}>
        Continue to payment
      </button>
    </form>
  );
}

После автодополнения важно показать отдельные поля, которые можно отредактировать. Это нужно для доступности и для особых случаев: в реальном адресе может быть номер квартиры или код домофона, которых нет в результате геокодирования. Автодополнение заполняет проверенную основу адреса, а остальное покупатель допишет сам.

Форматы адресов в странах ЕС

В разных странах ЕС адреса пишут по-разному, и от этого зависит как отображение, так и порядок полей в форме:

Германия (DEU): сначала улица, потом номер дома. Hauptstraße 42, 10115 Berlin. В немецких результатах API свойство housenumber правильно идёт после улицы.

Франция (FRA): номер дома стоит перед улицей. 42 rue de Rivoli, 75001 Paris. Свойство label возвращает адрес в формате, принятом в стране.

Нидерланды (NLD): индекс состоит из 4 цифр и 2 заглавных букв через пробел: 1012 LG. Проверяйте этот формат, если делите индекс на части для своей системы доставки.

Бельгия (BEL): в двуязычных регионах адрес может прийти на французском или нидерландском, в зависимости от муниципалитета.

MapAtlas Geocoding API учитывает всё это в поле label (адрес для человека в формате страны) и одновременно возвращает отдельные поля street, housenumber и postalcode. Если нужно, вы можете собрать отдельный макет формы для каждой страны.

Если нужно проверить уже существующую базу адресов, например почистить старую CRM перед запуском доставки, прочитайте статью Как проверить 10 000 адресов разом с помощью Geocoding API.

Производительность

Реализация выше отправляет один запрос к API после каждой паузы в наборе длиной 300 мс, а не на каждый символ, поэтому обычный адрес стоит несколько запросов. Если у интернет-магазина заметный поток заказов, поставьте перед Geocoding API серверный прокси, чтобы API-ключ никогда не попадал в клиентский код:

// pages/api/autocomplete.js (Next.js API route)
export default async function handler(req, res) {
  const { text, countryCode } = req.query;
  const url = new URL('https://gateway.mapmetrics-atlas.net/autocomplete/');
  url.searchParams.set('text',  text);
  url.searchParams.set('token',   process.env.MAPATLAS_KEY); // Server-side env var
  url.searchParams.set('size',  '6');
  url.searchParams.set('layers', 'address');
  if (countryCode) url.searchParams.set('boundary.country', countryCode);

  const response = await fetch(url.toString());
  const data     = await response.json();
  res.json(data);
}

Затем переключите хук на /api/autocomplete вместо прямого обращения к MapAtlas API. Так вы сможете добавить кэширование на границе сети (Vercel Edge Functions, Cloudflare Workers) и сократить число запросов к API для частых адресов.

Актуальные цены на Geocoding API и лимиты бесплатного тарифа есть на странице цен MapAtlas. Большинству интернет-магазинов на этапе разработки бесплатного тарифа для автодополнения хватает с запасом.

Итог

Одно поле автодополнения адреса может заметно поднять конверсию оформления заказа. Реализация простая: запрос к MapAtlas Geocoding API с задержкой, небольшой выпадающий список с навигацией с клавиатуры и форма, которая заполняет поля из выбранного результата.

Главные решения:

  • Задержка 300 мс, чтобы быстрый набор не порождал лишние запросы к API.
  • Минимум 3 символа, прежде чем отправлять запрос.
  • Ограничение по стране, если вы знаете, где живут ваши покупатели. Результаты становятся заметно точнее.
  • Всегда оставляйте возможность исправить заполненные поля вручную: для номера квартиры, кода домофона и поправок.
  • Прячьте API-ключ за серверным прокси в рабочей версии, чтобы он не попал в клиентские бандлы.

Чтобы показать место доставки на карте на странице подтверждения заказа, прочитайте статью Как добавить интерактивную карту на сайт.


Зарегистрируйтесь и получите бесплатный API-ключ MapAtlas, чтобы начать разработку. Geocoding API входит в бесплатный тариф, банковская карта не нужна.

Часто задаваемые вопросы

Как автодополнение адреса повышает конверсию при оформлении заказа?

Адрес это одно из полей, где при оформлении заказа приходится печатать больше всего, особенно на телефоне. Автодополнение сводит ввод к нескольким символам и одному касанию, отсекает ошибки формата, из-за которых срываются доставки, и снимает сомнение, правильно ли введён адрес. В большом исследовании оформления заказа Baymard Institute 9% вручную введённых адресов доставки содержали опечатки или ошибки в написании. Именно таких ошибок не бывает, когда покупатель выбирает подсказку.

Поддерживает ли MapAtlas Geocoding API европейские форматы адресов?

Да. API работает с форматами, принятыми в ЕС: немецким порядком, где номер дома стоит после улицы, французскими округами (arrondissements), нидерландскими индексами из четырёх цифр и двух букв и многоязычными адресами во всех странах ЕС. Результаты приходят в GeoJSON с разобранными компонентами адреса.

Как не отправлять лишние запросы к API при автодополнении?

Добавьте задержку (debounce) 250-300 мс в обработчик ввода, чтобы запрос уходил только после паузы в наборе. Кроме того, задайте минимальную длину ввода (3-4 символа), прежде чем отправлять запросы. Вместе эти две меры резко сокращают число запросов по сравнению с запросом на каждое нажатие: адрес из 20 символов, набранный в несколько приёмов, даёт несколько запросов вместо 20.

Оказалось полезным? Поделитесь.

Об авторе

Brent van der Heiden

Автор

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. Before that he co-founded MapMetrics, a community mapping and navigation app. He writes about geospatial infrastructure, map data, AI search visibility, and how location data powers the products people rely on every day.

Все статьи →
Вернуться в блог