Address autocomplete in React with the Locio package

About 6 minutes. Updated 18 September 2026.

Install the published React client and have an accessible address autocomplete working in a few minutes, with the debouncing, request cancellation and keyboard handling already done and every class name still yours.

There is a guide that builds this by hand, and it is worth reading if you want to know what the moving parts are. This one installs @locio-au/react instead, which is the same thing with the awkward bits already handled.

01Install it

sh
npm install @locio-au/react

React 18 or newer, as a peer dependency. Nothing else comes with it: no styles, no state library, no icon set.

02Drop the component in

AddressAutocomplete renders a labelled input and a listbox, implements the ARIA combobox pattern so arrow keys, Enter and Escape all work, and hands you the picked address. Results come from /v1/addresses.

src/CheckoutForm.tsxtsx
// src/CheckoutForm.tsx
import { AddressAutocomplete, addressId, type Address } from "@locio-au/react";
import { useState } from "react";

export function CheckoutForm() {
  const [address, setAddress] = useState<Address | null>(null);

  return (
    <form>
      <AddressAutocomplete
        publicKey={import.meta.env.VITE_LOCIO_KEY}
        label="Delivery address"
        onSelect={setAddress}
      />

      {address && (
        <input type="hidden" name="address_id" value={addressId(address)} />
      )}
    </form>
  );
}

Store addressId(address). It is the address's id in its country's register, which in Australia is the G-NAF Address Detail PID, sent under address_detail_pid as well. It is stable across releases for an address that has not changed, where the formatted line is not, so it is the thing to key your own records on rather than the text. addressId rather than address.id: the field is optional on the package's type, because a service one release behind sends only the G-NAF names, and the helper reads whichever arrived.

03Make it look like your form

Every slot takes a class of yours, and styled=false removes the package's own inline styles so there is nothing to override.

src/CheckoutForm.tsxtsx
// Your classes, on every slot. styled={false} drops the
// package's inline styles so nothing fights your stylesheet.
<AddressAutocomplete
  publicKey={import.meta.env.VITE_LOCIO_KEY}
  styled={false}
  classNames={{
    root: "field",
    label: "field-label",
    input: "field-input",
    list: "field-list",
    option: "field-option",
    status: "field-status",
  }}
  onSelect={setAddress}
/>

04Or take the hook and write your own markup

The component is a thin layer over a hook. The hook does the debounce, cancels the request in flight when the term changes, and keeps a late answer for an earlier prefix from overwriting a newer one.

src/Search.tsxtsx
// When you want your own markup entirely.
import { addressId, useAddressAutocomplete } from "@locio-au/react";

function Search() {
  // note and countryCode are what the service said about the country it
  // searched: somebody typing from a country with no address data gets no
  // results and a sentence saying so, which is the one to show them.
  const { term, setTerm, results, status, note, countryCode } = useAddressAutocomplete({
    publicKey: import.meta.env.VITE_LOCIO_KEY,
    debounceMs: 300,
    minLength: 3,
  });

  return (
    <div>
      <input value={term} onChange={(e) => setTerm(e.target.value)} />
      {status === "searching" && <p>Searching</p>}
      {note && <p>{note}</p>}
      <ul>
        {results.map((a) => (
          <li key={addressId(a)}>{a.formatted}</li>
        ))}
      </ul>
      {countryCode && <p>Searched {countryCode}</p>}
    </div>
  );
}

What it costs

One unit per request. The default debounce is 300 milliseconds and the default minimum is three characters, so a completed address is usually three or four requests. Refused requests are free.

The key

A public key, always. It carries an origin allow list, so a copy lifted from your page does nothing anywhere else. The client refuses a secret key outright when it sees a browser, rather than letting one reach your users. Create a key.

Related