Forms
Every picker is a controlled component, so it plugs into any form library. In a form you usually want a field that opens the picker: SelectPicker and the picker fields (DatePickerField, TimePickerField, DateTimePickerField, WheelPickerField, DurationPickerField, MonthPickerField, ColorPickerField, EmojiPickerField) render one, and report the value through onChange (select) or onConfirm (fields). Their onClose is the natural place for onBlur.
React Hook Form​
import { useForm, Controller } from 'react-hook-form';
import { Text, View, Button } from 'react-native';
import { SelectPicker, DatePickerField, TimePickerField } from '@elik56a/react-native-pickers';
import type { DateRange } from '@elik56a/react-native-pickers';
type FormValues = {
country: string | null;
tags: string[];
date: Date | null;
time: Date;
stay: DateRange;
};
export function BookingForm() {
const { control, handleSubmit, formState: { errors } } = useForm<FormValues>({
defaultValues: { country: null, tags: [], date: null, time: new Date(), stay: { start: null, end: null } },
});
return (
<View style={{ gap: 16 }}>
<Controller
control={control}
name="country"
rules={{ required: 'Pick a country' }}
render={({ field }) => (
<SelectPicker label="Country" options={countries} value={field.value} onChange={field.onChange} onClose={field.onBlur} />
)}
/>
{errors.country && <Text style={{ color: 'crimson' }}>{errors.country.message}</Text>}
<Controller
control={control}
name="tags"
rules={{ validate: (v) => v.length > 0 || 'Pick at least one tag' }}
render={({ field }) => <SelectPicker multiple label="Tags" options={tags} value={field.value} onChange={field.onChange} />}
/>
<Controller
control={control}
name="stay"
rules={{ validate: (r) => (!!r.start && !!r.end) || 'Choose check-in and check-out' }}
render={({ field }) => (
<DatePickerField
label="Stay"
placeholder="Add dates"
mode="range"
value={field.value}
onConfirm={field.onChange}
onClose={field.onBlur}
minDate={new Date()}
/>
)}
/>
{errors.stay && <Text style={{ color: 'crimson' }}>{errors.stay.message}</Text>}
<Controller
control={control}
name="time"
render={({ field }) => <TimePickerField label="Arrival" value={field.value} onConfirm={field.onChange} onClose={field.onBlur} />}
/>
<Button title="Submit" onPress={handleSubmit(console.log)} />
</View>
);
}
field.onChange receives the first argument, which is the value, so extra arguments such as the option from SelectPicker are ignored.
Options from your API​
A field whose options come from a server takes loadOptions instead of a fixed list. It searches as the user types, with a loader, and the form only ever sees the picked value:
<Controller
control={control}
name="assignee"
rules={{ required: 'Assign someone' }}
render={({ field }) => (
<SelectPicker
label="Assignee"
placeholder="Search people"
loadOptions={(query) => api.searchUsers(query).then((users) => users.map((u) => ({ label: u.name, value: u.id })))}
value={field.value}
onChange={field.onChange}
onClose={field.onBlur}
/>
)}
/>
The field keeps showing the picked person's name after later searches stop returning them. For a full form with server search, see the flight search form recipe.
Optional values​
A field with clearable shows a × button while it has a value. Reset the form value in onClear:
<Controller
control={control}
name="date"
render={({ field }) => (
<DatePickerField label="Due date" value={field.value} onConfirm={field.onChange} clearable onClear={() => field.onChange(null)} />
)}
/>
Inline pickers​
The inline pickers (DatePicker, TimePicker, WheelPicker…) take value + onChange the same way, for a picker that's always on screen:
<Controller control={control} name="time" render={({ field }) => <TimePicker value={field.value} onChange={field.onChange} />} />
Formik​
<Formik initialValues={{ birthday: new Date(2000, 0, 1), size: 'M' }} onSubmit={save}>
{({ values, setFieldValue }) => (
<>
<DatePickerField label="Birthday" display="wheel" value={values.birthday} onConfirm={(d) => setFieldValue('birthday', d)} />
<WheelPickerField label="Size" items={['S', 'M', 'L']} value={values.size} onConfirm={(v) => setFieldValue('size', v)} />
</>
)}
</Formik>
Sending dates to an API​
Pickers work with local Date objects. Serialize them for your API deliberately:
import { toDateKey } from '@elik56a/react-native-pickers';
// A calendar day (no time zone): '2026-10-14'
const day = toDateKey(date);
// An instant: '2026-10-14T07:30:00.000Z'
const instant = time.toISOString();
// Parse a 'YYYY-MM-DD' back to a local date (don't use new Date('2026-10-14'), which parses as UTC)
const [y, m, d] = '2026-10-14'.split('-').map(Number);
const local = new Date(y, m - 1, d);
date.toISOString().slice(0, 10) converts to UTC first, and can give yesterday or tomorrow depending on the user's time zone. Use toDateKey for calendar days.
Zod schema​
import { z } from 'zod';
const schema = z.object({
stay: z
.object({ start: z.date().nullable(), end: z.date().nullable() })
.refine((r) => r.start && r.end, 'Choose both dates'),
country: z.string({ required_error: 'Pick a country' }),
});