Skip to main content

AutoCompleteInput

Text input with a suggestion dropdown. Shares the same row layout and styling primitives as Input (label, icons, validation, clear button), with an animated suggestion panel underneath that expands/collapses to its measured content height.

Inside a ScrollView

The suggestion panel renders its rows in a ScrollView. If AutoCompleteInput sits inside your own ScrollContainer/ScrollView (the common case for forms), that outer ScrollView must also have keyboardShouldPersistTaps="handled". Without it, the outer ScrollView swallows the first tap on a suggestion to dismiss the keyboard instead of passing it through — so selection silently does nothing.

<ScrollContainer keyboardShouldPersistTaps="handled">
<AutoCompleteInput ... />
</ScrollContainer>

Basic Usage

import { useState } from 'react';
import { AutoCompleteInput } from '@multinaire/expo-ui';

const CITIES = ['Bangkok', 'Berlin', 'Boston', 'Brisbane', 'Budapest'];

function CityField() {
const [value, setValue] = useState('');

return (
<AutoCompleteInput
placeholder="Search a city..."
leading="Location"
value={value}
onChangeText={setValue}
items={CITIES.map(city => ({ text: city, value: city }))}
searchPredicate={(city, filter) =>
city.toLowerCase().includes(filter.toLowerCase())
}
onChange={city => console.log('Selected:', city)}
/>
);
}

Props

PropTypeDefaultDescription
itemsAutoCompleteInputItemData<T>[]Suggestions shown below the input
valuestringCurrent value of the input
onChangeText(text: string) => voidCallback invoked on every text change
selectedItemTCurrently selected item value. Compared against each suggestion's value to highlight the matching one. A suggestion is also highlighted when its text matches the current input value
onChange(value: T, item: AutoCompleteInputItemData<T>) => voidCallback invoked when a suggestion is selected. The input is automatically filled with the suggestion's text
onClear() => voidCallback invoked when the clear button is pressed. Use this to also reset any externally-held selectedItem state
searchPredicate(item: T, filter: string) => booleanClient-side filter run against items for the current value. Omit when items is already filtered externally (e.g. a server-side search wired up via onChangeText)
isLoadingbooleanfalseShow a loading state in place of the suggestion list (e.g. while fetching results)
minCharactersnumber1Minimum input length before suggestions are shown
maxVisibleItemsnumber5Maximum number of suggestion rows visible before the list scrolls
noResultsTextstringText shown when there are no matching suggestions
highlightMatchbooleantrueHighlight the portion of each suggestion's text that matches the current input value
titlestringField label displayed above the input
requiredbooleanfalseMark the field as required. Displays a visual indicator
allowClearbooleanfalseShow a clear (×) button to reset the value
leadingIconName | ReactElementLeading icon name or a custom React element rendered at the start of the input
trailingIconName | ReactElementTrailing icon name or a custom React element rendered at the end of the input
errorTextstringError message displayed below the input
keyboardTypeKeyboardTypeOptionsKeyboard type shown when the input is focused
flexnumberFlex grow value
marginSpacingPropsOuter margin
testIDstringTest identifier for UI automation

Examples

Omit searchPredicate and pass in already-filtered items from your own fetch logic; toggle isLoading while the request is in flight.

const [value, setValue] = useState('');
const [items, setItems] = useState<AutoCompleteInputItemData<string>[]>([]);
const [isLoading, setIsLoading] = useState(false);

<AutoCompleteInput
placeholder="Search users..."
value={value}
onChangeText={async text => {
setValue(text);
setIsLoading(true);
const results = await searchUsers(text);
setItems(results.map(user => ({ text: user.name, value: user.id })));
setIsLoading(false);
}}
items={items}
isLoading={isLoading}
onChange={userId => console.log('Selected user:', userId)}
/>

With Leading Icons per Suggestion

<AutoCompleteInput
placeholder="Add a member..."
value={value}
onChangeText={setValue}
items={members.map(member => ({
text: member.name,
value: member.id,
leading: member.avatar,
}))}
searchPredicate={(id, filter) =>
members
.find(m => m.id === id)!
.name.toLowerCase()
.includes(filter.toLowerCase())
}
onChange={id => addMember(id)}
/>

No Results / Minimum Characters

<AutoCompleteInput
placeholder="Search a city..."
value={value}
onChangeText={setValue}
items={items}
searchPredicate={(city, filter) =>
city.toLowerCase().includes(filter.toLowerCase())
}
minCharacters={2}
noResultsText="No cities found"
onChange={setSelectedCity}
/>