Dropdown Item picker with search and autocomplete (typeahead) functionality for react native
Run expo snack demo @onmotion/react-native-autocomplete-dropdown
Run: npm install --save react-native-autocomplete-dropdown
or yarn add react-native-autocomplete-dropdown
Make sure react-native-vector-icons is installed. Follow the guides https://github.com/oblador/react-native-vector-icons
yarn add react-native-vector-icons
Run: npx pod-install
for install react-native-vector-iconsreact-native-vector-icons dependency (if not installed yet).
Follow the guides from https://github.com/oblador/react-native-vector-icons#android for install react-native-vector-icons dependency (if not installed yet).
import the package
import { AutocompleteDropdown } from 'react-native-autocomplete-dropdown';
dataSet
property must be an array of objects or null. Object required keys are:
{
id: 'some uniq string id',
title: 'list item title'
}
const [selectedItem, setSelectedItem] = useState(null);
<AutocompleteDropdown
clearOnFocus={false}
closeOnBlur={true}
closeOnSubmit={false}
initialValue={{ id: '2' }} // or just '2'
onSelectItem={setSelectedItem}
dataSet={[
{ id: '1', title: 'Alpha' },
{ id: '2', title: 'Beta' },
{ id: '3', title: 'Gamma' },
]}
/>;
import React, { memo, useCallback, useRef, useState } from 'react'
import { Button, Dimensions, Text, View, Platform } from 'react-native'
import { AutocompleteDropdown } from 'react-native-autocomplete-dropdown'
import Feather from 'react-native-vector-icons/Feather'
Feather.loadFont()
export const RemoteDataSetExample2 = memo(() => {
const [loading, setLoading] = useState(false)
const [suggestionsList, setSuggestionsList] = useState(null)
const [selectedItem, setSelectedItem] = useState(null)
const dropdownController = useRef(null)
const searchRef = useRef(null)
const getSuggestions = useCallback(async q => {
const filterToken = q.toLowerCase()
console.log('getSuggestions', q)
if (typeof q !== 'string' || q.length < 3) {
setSuggestionsList(null)
return
}
setLoading(true)
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
const items = await response.json()
const suggestions = items
.filter(item => item.title.toLowerCase().includes(filterToken))
.map(item => ({
id: item.id,
title: item.title,
}))
setSuggestionsList(suggestions)
setLoading(false)
}, [])
const onClearPress = useCallback(() => {
setSuggestionsList(null)
}, [])
const onOpenSuggestionsList = useCallback(isOpened => {}, [])
return (
<>
<View
style={[
{ flex: 1, flexDirection: 'row', alignItems: 'center' },
Platform.select({ ios: { zIndex: 1 } }),
]}>
<AutocompleteDropdown
ref={searchRef}
controller={controller => {
dropdownController.current = controller
}}
// initialValue={'1'}
direction={Platform.select({ ios: 'down' })}
dataSet={suggestionsList}
onChangeText={getSuggestions}
onSelectItem={item => {
item && setSelectedItem(item.id)
}}
debounce={600}
suggestionsListMaxHeight={Dimensions.get('window').height * 0.4}
onClear={onClearPress}
// onSubmit={(e) => onSubmitSearch(e.nativeEvent.text)}
onOpenSuggestionsList={onOpenSuggestionsList}
loading={loading}
useFilter={false} // set false to prevent rerender twice
textInputProps={{
placeholder: 'Type 3+ letters (dolo...)',
autoCorrect: false,
autoCapitalize: 'none',
style: {
borderRadius: 25,
backgroundColor: '#383b42',
color: '#fff',
paddingLeft: 18,
},
}}
rightButtonsContainerStyle={{
right: 8,
height: 30,
alignSelf: 'center',
}}
inputContainerStyle={{
backgroundColor: '#383b42',
borderRadius: 25,
}}
suggestionsListContainerStyle={{
backgroundColor: '#383b42',
}}
containerStyle={{ flexGrow: 1, flexShrink: 1 }}
renderItem={(item, text) => <Text style={{ color: '#fff', padding: 15 }}>{item.title}</Text>}
ChevronIconComponent={<Feather name="chevron-down" size={20} color="#fff" />}
ClearIconComponent={<Feather name="x-circle" size={18} color="#fff" />}
inputHeight={50}
showChevron={false}
closeOnBlur={false}
// showClear={false}
/>
<View style={{ width: 10 }} />
<Button style={{ flexGrow: 0 }} title="Toggle" onPress={() => dropdownController.current.toggle()} />
</View>
<Text style={{ color: '#668', fontSize: 13 }}>Selected item id: {JSON.stringify(selectedItem)}</Text>
</>
)
})
More examples see at https://github.com/onmotion/react-native-autocomplete-dropdown/tree/main/example
Run
cd example
yarn install
yarn add react-native-vector-icons
npx pod-install
npm run ios
Option | Description | Type | Default |
---|---|---|---|
dataSet |
set of list items | array | null |
initialValue |
string (id) or object that contain id | string | object | null |
loading |
loading state | bool | false |
useFilter |
whether use local filter by dataSet (useful set to false for remote filtering to prevent rerender twice) | bool | true |
showClear |
show clear button | bool | true |
showChevron |
show chevron (open/close) button | bool | true |
closeOnBlur |
whether to close dropdown on blur | bool | false |
closeOnSubmit |
whether to close dropdown on submit | bool | false |
clearOnFocus |
whether to clear typed text on focus | bool | true |
debounce |
wait ms before call onChangeText |
number | 0 |
suggestionsListMaxHeight |
max height of dropdown | number | 200 |
direction |
"up" or "down" | string | down + auto calculate |
position |
"relative" or "absolute" | string | relative |
bottomOffset |
for calculate dropdown direction (e.g. tabbar) | number | 0 |
onChangeText |
event textInput onChangeText | function | |
onSelectItem |
event onSelectItem | function | |
onOpenSuggestionsList |
event onOpenSuggestionsList | function | |
onChevronPress |
event onChevronPress | function | |
onClear |
event on clear button press | function | |
onSubmit |
event on submit KB button press | function | |
onBlur |
event fired on text input blur | function | |
onFocus |
event on focus text input | function | |
renderItem |
JSX for render item (item, searchText) => JSX | null if return null then the element will not be displayed |
function | item.title |
controller |
return reference to module controller with methods close, open, toggle, clear, setInputText, setItem | function | |
containerStyle |
ViewStyle | ||
rightButtonsContainerStyle |
ViewStyle | ||
suggestionsListContainerStyle |
ViewStyle | ||
suggestionsListTextStyle |
TextStyle | styles of suggestions list text items | |
ChevronIconComponent |
React.Component | Feather chevron icon | |
ClearIconComponent |
React.Component | Feather x icon | |
removed in 2.0.0 based on FlatList | React.Component name | ScrollView that provide suggestions content | |
EmptyResultComponent |
replace the default `` Component on empty result | React.Component | |
InputComponent |
input element component | React.ComponentType | TextInput |
emptyResultText |
replace the default "Nothing found" text on empty result | string | "Nothing found" |
textInputProps |
text input props | TextInputProps | |
flatListProps |
props for \ component | FlatListProps\ |
As decribed here https://docs.expo.dev/ui-programming/z-index/ on iOS for absolute positioned items we must respect the zIndex of the element parent.
So for example if you do smth like
<View style={[styles.section]}>
<Text style={styles.sectionTitle}>First</Text>
<AutocompleteDropdown
dataSet={[
{ id: '1', title: 'Alpha' },
{ id: '2', title: 'Beta' },
{ id: '3', title: 'Gamma' },
]}
/>
</View>
<View style={[styles.section]}>
<Text style={styles.sectionTitle}>Second</Text>
<AutocompleteDropdown
dataSet={[
{ id: '1', title: 'Alpha' },
{ id: '2', title: 'Beta' },
{ id: '3', title: 'Gamma' },
]}
/>
</View>
But if it change with calculated zIndex:
<View
style={[styles.section, Platform.select({ ios: { zIndex: 10 } })]}>
<Text style={styles.sectionTitle}>First</Text>
<AutocompleteDropdown
dataSet={[
{ id: '1', title: 'Alpha' },
{ id: '2', title: 'Beta' },
{ id: '3', title: 'Gamma' },
]}
/>
</View>
<View
style={[styles.section, Platform.select({ ios: { zIndex: 9 } })]}>
<Text style={styles.sectionTitle}>Second</Text>
<AutocompleteDropdown
dataSet={[
{ id: '1', title: 'Alpha' },
{ id: '2', title: 'Beta' },
{ id: '3', title: 'Gamma' },
]}
/>
</View>
it will be rendered as expected
And the same, if you want render dropdown list to up direction, you should switch zIndexes respectively
More info about this behaviour: https://docs.expo.dev/ui-programming/z-index/