Flight search form
A booking form like the ones on airline and travel sites. The airports come from a server: the list starts empty and asks for a city, airport or code, then searches as the user types, with a loader. Trip type and cabin are segmented controls, the dates switch between one date and a range with the trip type, and Search flights checks everything before it goes.
Try it: open From (a dropdown on the web, a bottom sheet on the iOS and Android tabs): the list is empty and says Type a city, airport or code. Type lon: the loader shows Finding airports… while the fake server answers, then London's two airports appear. Type xyz and it says No airport matches.
<View style={styles.form}> <HorizontalList items={TRIPS} value={trip} onChange={setTrip} accessibilityLabel="Trip type" /> <View> <SelectPicker label="From" placeholder="City or airport" loadOptions={searchAirports} value={from} onChange={setFrom} searchPrompt="Type a city, airport or code" loadingText="Finding airports…" emptyText="No airport matches" /> <FieldError message={submitted && errors.from} /> </View> <View> <SelectPicker label="To" placeholder="City or airport" loadOptions={searchAirports} value={to} onChange={setTo} searchPrompt="Type a city, airport or code" loadingText="Finding airports…" emptyText="No airport matches" /> <FieldError message={submitted && errors.to} /> </View> <View> {trip === 'round' ? ( <DatePickerField label="Dates" placeholder="Depart → Return" mode="range" value={dates} onConfirm={setDates} minDate={new Date()} /> ) : ( <DatePickerField label="Date" placeholder="Depart" value={depart} onConfirm={setDepart} minDate={new Date()} /> )} <FieldError message={submitted && errors.dates} /> </View> <WheelPickerField label="Travellers" items={TRAVELLERS} value={travellers} onConfirm={setTravellers} /> <View> <Text style={[styles.label, { color: colors.textSecondary }]}>Cabin</Text> <HorizontalList items={CABINS} value={cabin} onChange={setCabin} accessibilityLabel="Cabin" /> </View> <Pressable onPress={submit} style={[styles.button, { borderRadius: radius.md, backgroundColor: colors.primary }]} accessibilityRole="button" > <Text style={[styles.buttonText, { color: colors.onPrimary }]}>Search flights</Text> </Pressable> {summary ? <Text style={[styles.summary, { color: colors.text }]}>✈️ {summary}</Text> : null} </View>
How it's built
- Airports from a server.
loadOptions={searchAirports}does the searching: it waits for a pause in typing, showsloadingTextfrom the keystroke until the airports arrive, and ignores answers to older queries. - The right text at each step. Before typing, the empty list shows
searchPrompt; after a search with no results,emptyText. To list something before typing instead (popular or recent airports), pass them asoptions. - Search by anything. The server matches city, code and airport name, so
tlv,heathrowandnew yorkall work. The list shows what it returns without filtering again. - The picked airport keeps its label after a later search no longer returns it.
- Trip type is a segmented
HorizontalList; it swaps the dates field between a range and a single date. Cabin is another, with four options that scroll if the screen is narrow. - Theme colours. The labels, errors and Search flights read
usePickerTheme(), so the form matches the pickers in dark mode and in each design; Material's fields float their labels, the others show them above. - Validation runs on submit and shows a message under each field, including From and To being the same airport.
With your API
Replace the stand-in with a real request. loadOptions takes any function that returns a promise of options:
const searchAirports = async (query: string) => {
const res = await fetch(`https://api.example.com/airports?q=${encodeURIComponent(query)}`);
if (!res.ok) throw new Error(`Airport search failed: ${res.status}`);
const airports: Airport[] = await res.json();
return airports.map(toOption);
};
A rejected promise shows emptyText. Set searchDebounce (300 ms by default) higher for a slow or rate-limited API.
In a form library, wire the fields the same way as in the forms guide: value={field.value} and onChange={field.onChange} on the SelectPicker, onConfirm={field.onChange} on the fields.