Skip to content
AutoSuggesAutoSugges home
Start free
Menu
Appearance
Appearance: System.

Add AutoSugges autocomplete to an Expo app

Compose the autocomplete from React Native primitives, with the accessibility semantics supplied by the SDK’s native binding.

How do I add autocomplete to an Expo or React Native app?

Create the same client with createAutosuggesClient, call bootstrap() once, and drive the useAutosuggesCombobox hook from @autosugges/client/native. Spread its accessibility props onto your own TextInput, FlatList and rows. The native binding provides hooks and prop mappers rather than components, so you keep full control of layout and styling.

What you will have

A working, screen-reader-correct autocomplete in an Expo app, using the same client and the same query policy as the web surfaces.

Before you start

  • An AutoSugges list that has been published at least once. An unpublished list has nothing to serve yet and returns list_not_published.
  • The publishable key issued for the application that will query it.
  • The runtime base URL for the environment you are targeting. AutoSugges does not have one fixed public hostname baked into the SDK — the dashboard's integration panel shows the origin for your environment.
  • An Expo (or bare React Native) project with React 18 or newer.

Values you supply

Each value this integration needs, where it comes from, and whether it is public
ValuePlaceholderWhere it comes from
baseUrlRequired · public by designYOUR_RUNTIME_BASE_URLThe AutoSugges dashboard's integration panel, for the environment you are deploying to. The origin of the AutoSugges runtime — scheme and host, no trailing slash and no path. The SDK appends /v1/... itself.
publishableKeyRequired · public by designYOUR_PUBLISHABLE_KEYThe AutoSugges dashboard, under the application that will make the queries. Identifies the consumer, the application, the subscription, the canonical list and the query policy in a single server-side lookup. The client never supplies a list id, tenant id or version hash — if a generated integration is passing one of those, it is wrong.
accessTokenOptional · never in client codeYOUR_SERVER_MINTED_ACCESS_TOKENYour own backend, which mints it from your AutoSugges credentials. Only for a list whose policy requires a token. Short-lived, sent as authorization: Bearer <token>, and never minted, stored or hardcoded in client code.

Steps

  1. Create one client. createAutosuggesClient works unchanged in React Native: its transport is the standard Fetch API, which the runtime provides natively. No polyfill.
  2. Bootstrap once. Call client.bootstrap() at startup so minChars and debounce come from the list's policy rather than a guessed constant.
  3. Drive the hook. useAutosuggesCombobox({ source, onSelect, onError }) owns query, debounce, cancellation and active-option state. Do not reimplement any of it.
  4. Spread the accessibility props. Spread getInputAccessibilityProps(state), getListAccessibilityProps() and getOptionAccessibilityProps(state, index) onto your TextInput, FlatList and rows. @autosugges/client/native exports mappers and hooks, never components — React Native has no DOM, so the consumer owns the elements.
  5. Announce explicitly on iOS. iOS has no live region. Speak activeOptionAnnouncement(state) and state.announcement through AccessibilityInfo.announceForAccessibility in effects.

Install

npm install @autosugges/client

Code

CityField.tsx

import { useEffect, useState } from 'react';
import { AccessibilityInfo, FlatList, Pressable, Text, TextInput } from 'react-native';
import { createAutosuggesClient } from '@autosugges/client';
import {
  activeOptionAnnouncement,
  getInputAccessibilityProps,
  getListAccessibilityProps,
  getOptionAccessibilityProps,
  handleNativeKeyPress,
  useAutosuggesCombobox,
} from '@autosugges/client/native';

// The SDK's transport is the standard Fetch API, which React Native provides
// natively. No polyfill, and no native module.
const autosugges = createAutosuggesClient({
  baseUrl: 'YOUR_RUNTIME_BASE_URL',
  publishableKey: 'YOUR_PUBLISHABLE_KEY',
});
void autosugges.bootstrap().catch(() => {});

export function CityField() {
  const [city, setCity] = useState('');
  const [errorCode, setErrorCode] = useState<string | undefined>();

  // /native exports hooks and prop mappers, not components: React Native has no
  // DOM, so the consumer owns the elements and the binding owns their semantics.
  const combobox = useAutosuggesCombobox({
    source: autosugges,
    onSelect: (item) => setCity(item.value),
    onError: (error) => setErrorCode(error.code),
  });
  const { state } = combobox;

  // iOS has no live region, so announcements are spoken explicitly.
  const spoken = activeOptionAnnouncement(state);
  useEffect(() => {
    if (spoken !== undefined) AccessibilityInfo.announceForAccessibility(spoken);
  }, [spoken]);

  return (
    <>
      <TextInput
        {...getInputAccessibilityProps(state)}
        accessibilityLabel="City"
        value={state.inputValue}
        onChangeText={combobox.setInputValue}
        onKeyPress={(event) => handleNativeKeyPress(combobox, event.nativeEvent.key)}
      />
      <FlatList
        {...getListAccessibilityProps()}
        data={state.items}
        keyExtractor={(item) => item.itemId}
        renderItem={({ item, index }) => (
          <Pressable
            {...getOptionAccessibilityProps(state, index)}
            accessible
            onPress={() => combobox.selectIndex(index)}
          >
            <Text>{item.displayValue}</Text>
          </Pressable>
        )}
      />
    </>
  );
}

Security

  • A mobile bundle is not a secret store: anything embedded in the app can be extracted. Only the publishable key and the base URL belong there.

Check that it works

  1. Run on a device or simulator and confirm suggestions appear after the configured debounce.
  2. Enable VoiceOver (iOS) or TalkBack (Android) and confirm the input announces its expanded state, the active option, and the result count.
  3. Confirm each touch target meets the 24×24dp minimum — use the binding’s hit-slop helper where the visual size is smaller.
  4. Put the device on a slow or offline connection and confirm the error state renders rather than the app hanging or crashing.
  5. Confirm no request is issued below minQueryChars.

Typing into the field returns suggestions from the published list, the control is fully operable and announced by the platform screen reader, and selecting a row fires onSelect with the canonical item.

Try it live

Paste a publishable key from one of your published lists to run a real query against this environment’s runtime — the same @autosugges/client the code above uses.

A published list's publishable key — a public identifier, safe to paste here.

Paste a publishable key to try a live query.

Notes

  • Never hardcode a minimum query length or a debounce interval. Both come from client.policy, which the SDK fills from the bootstrap response and updates in place. The constants the SDK falls back to before the first successful bootstrap are documented defaults, not the list’s real policy.
  • Render displayValue, store value. They differ: displayValue is resolved for the requested locale at compile time.
  • A selection report is optional. Omitting it costs ranking quality over time and nothing else; it is never required for a query to work.
  • There is no origin header on a native request, so origin allow-listing does not protect a mobile app. Quota, rate limiting and (for protected lists) access tokens are what bound abuse there.

Recipes for specific datasets

The same integration, specialised to the datasets whose behaviour changes the code.

Frequently asked questions

What is the minimum number of characters before a query runs?
Two by default. A list owner can change the minimum, but it can never be 1. The client reads the real value from bootstrap and sends no request below it, so do not hardcode it.
How long does the client wait after typing before it queries?
150 milliseconds by default. The value is delivered through bootstrap and a list owner can raise it, so the client uses the policy value rather than a constant.
Where does the publishable key go?
In the key query parameter of the request, for example /v1/query?key=YOUR_PUBLISHABLE_KEY, never in a header. The key is public by design and safe in client code. Secret keys never belong in a browser or mobile bundle, and a proxy to hide the publishable key is unnecessary.
What happens when my query quota runs out?
The runtime returns a structured quota_exceeded response that carries no items and no placeholder suggestions, so exhaustion is never mistaken for no results. Render it from the typed error code and prompt an upgrade.
Does AutoSugges work in React Native?
Yes. The same client runs in React Native because its transport is the standard Fetch API, and @autosugges/client/native provides hooks and accessibility prop mappers for Expo and bare React Native apps. See the Expo guide.
Does origin allow-listing protect a mobile app?
No. A native request carries no Origin header, so quota, rate limiting and, for protected lists, access tokens are what bound abuse in an app.