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
valuestring—Current value of the input
onChangeText(text: string) => void—Callback invoked on every text change
selectedItemT—Currently 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>) => void—Callback invoked when a suggestion is selected. The input is automatically filled with the suggestion's text
onClear() => void—Callback invoked when the clear button is pressed. Use this to also reset any externally-held selectedItem state
searchPredicate(item: T, filter: string) => boolean—Client-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
noResultsTextstring—Text shown when there are no matching suggestions
highlightMatchbooleantrueHighlight the portion of each suggestion's text that matches the current input value
titlestring—Field label displayed above the input
requiredbooleanfalseMark the field as required. Displays a visual indicator
allowClearbooleanfalseShow a clear (×) button to reset the value
leadingIconName | ReactElement—Leading icon name or a custom React element rendered at the start of the input
trailingIconName | ReactElement—Trailing icon name or a custom React element rendered at the end of the input
errorTextstring—Error message displayed below the input
autoFocusboolean—Focus the field as soon as it mounts, opening the keyboard
onSubmit(text: string) => void—Callback invoked when the user submits the input (e.g. presses Enter)
keyboardTypeKeyboardTypeOptions—Keyboard type shown when the input is focused
flexnumber—Flex grow value
marginSpacingProps—Outer margin
testIDstring—Test 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}
/>