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.
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 no artifact at the edge 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
| Value | Placeholder | Where it comes from |
|---|---|---|
| baseUrlRequired · public by design | YOUR_RUNTIME_BASE_URL | The AutoSugges dashboard's integration panel, for the environment you are deploying to. The origin of the AutoSugges runtime Worker — scheme and host, no trailing slash and no path. The SDK appends `/v1/...` itself. |
| publishableKeyRequired · public by design | YOUR_PUBLISHABLE_KEY | The 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 code | YOUR_SERVER_MINTED_ACCESS_TOKEN | Your 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
- Create one client. createAutosuggesClient works unchanged in React Native: its transport is the standard Fetch API, which the runtime provides natively. No polyfill.
- Bootstrap once. Call client.bootstrap() at startup so minChars and debounce come from the list's policy rather than a guessed constant.
- Drive the hook. useAutosuggesCombobox({ source, onSelect, onError }) owns query, debounce, cancellation and active-option state. Do not reimplement any of it.
- 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.
- Announce explicitly on iOS. iOS has no live region. Speak activeOptionAnnouncement(state) and state.announcement through AccessibilityInfo.announceForAccessibility in effects.
Install
Bundle `@autosugges/client` with Metro the way you bundle any other workspace dependency. It is not on the public npm registry yet — do not generate an `npm install @autosugges/client` command.
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 compiled into the app can be extracted. Only the publishable key and the base URL belong there.
Check that it works
- Run on a device or simulator and confirm suggestions appear after the configured debounce.
- Enable VoiceOver (iOS) or TalkBack (Android) and confirm the input announces its expanded state, the active option, and the result count.
- Confirm each touch target meets the 24×24dp minimum — use the binding’s hit-slop helper where the visual size is smaller.
- Put the device on a slow or offline connection and confirm the error state renders rather than the app hanging or crashing.
- 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 (PRD §12).
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 (`DEC-LIST-002`, `DEC-LIST-003`). 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.