Pickers, Fecha/Hora e Inputs Nativos
Trade-offs entre pickers de plataforma e interfaz personalizada.
Busca en todas las páginas de la documentación
Trade-offs entre pickers de plataforma e interfaz personalizada.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
npx expo install @react-native-community/datetimepicker @react-native-picker/pickerimport { useState } from "react";
import { Platform, Pressable, Text, View } from "react-native";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import { Picker } from "@react-native-picker/picker";
export function AppointmentFields() {
const [date, setDate] = useState(new Date());
const [showDate, setShowDate] = useState(false);
const [timezone, setTimezone] = useState("America/New_York");
const onDateChange = (_event: DateTimePickerEvent, selected?: Date) => {
if (Platform.OS === "android") setShowDate(false);
if (selected) setDate(selected);
};
return (
<View>
<Pressable
onPress={() => setShowDate(true)}
accessibilityRole="button"
accessibilityLabel="Fecha de la cita"
accessibilityHint="Abre el selector de fecha"
>
<Text>{date.toLocaleDateString()}</Text>
</Pressable>
{showDate && (
<DateTimePicker
value={date}
mode="date"
display={Platform.OS === "ios" ? "spinner" : "default"}
onChange={onDateChange}
/>
)}
<Picker
selectedValue={timezone}
onValueChange={setTimezone}
accessibilityLabel="Zona horaria"
>
<Picker.Item label="Eastern" value="America/New_York" />
<Picker.Item label="Pacific" value="America/Los_Angeles" />
</Picker>
</View>
);
}Cuándo usarlo: Cualquier campo de formulario donde la entrada de texto libre es propensa a errores - fechas de nacimiento, espacios de citas, selección de país/región, u opciones enumeradas con más de unos pocos valores.
import { useCallback, useState } from "react";
import {
Modal,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import { Picker } from "@react-native-picker/picker";
type FormState = {
date: Date;
time: Date;
durationMinutes: number;
};
const DURATIONS = [15, 30, 45, 60] as const;
const formatDate = (d: Date) =>
new Intl.DateTimeFormat(undefined, {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
}).format(d);
const formatTime = (d: Date) =>
new Intl.DateTimeFormat(undefined, {
hour: "numeric",
minute: "2-digit",
}).format(d);
export function BookingForm({
onSubmit,
}: {
onSubmit: (value: FormState) => void;
}) {
const [form, setForm] = useState<FormState>({
date: new Date(),
time: new Date(),
durationMinutes: 30,
});
const [activePicker, setActivePicker] = useState<
"date" | "time" | null
>(null);
const closePicker = useCallback(() => setActivePicker(null), []);
const onPickerChange = useCallback(
(field: "date" | "time") =>
(_event: DateTimePickerEvent, selected?: Date) => {
if (Platform.OS === "android") closePicker();
if (!selected) return;
setForm((prev) => ({ ...prev, [field]: selected }));
},
[closePicker],
);
const iosPicker = activePicker ? (
<Modal transparent animationType="slide" onRequestClose={closePicker}>
<Pressable style={styles.backdrop} onPress={closePicker} />
<View style={styles.sheet}>
<View style={styles.sheetHeader}>
<Pressable onPress={closePicker} accessibilityRole="button">
<Text style={styles.done}>Hecho</Text>
</Pressable>
</View>
<DateTimePicker
value={form[activePicker]}
mode={activePicker}
display="spinner"
onChange={onPickerChange(activePicker)}
/>
</View>
</Modal>
) : null;
const androidPicker =
Platform.OS === "android" && activePicker ? (
<DateTimePicker
value={form[activePicker]}
mode={activePicker}
onChange={onPickerChange(activePicker)}
/>
) : null;
return (
<View style={styles.container}>
<Text style={styles.label}>Fecha</Text>
<Pressable
style={styles.field}
onPress={() => setActivePicker("date")}
accessibilityRole="button"
accessibilityLabel={`Fecha, ${formatDate(form.date)}`}
>
<Text>{formatDate(form.date)}</Text>
</Pressable>
<Text style={styles.label}>Hora</Text>
<Pressable
style={styles.field}
onPress={() => setActivePicker("time")}
accessibilityRole="button"
accessibilityLabel={`Hora, ${formatTime(form.time)}`}
>
<Text>{formatTime(form.time)}</Text>
</Pressable>
<Text style={styles.label}>Duración</Text>
<View style={styles.pickerWrap}>
<Picker
selectedValue={form.durationMinutes}
onValueChange={(durationMinutes) =>
setForm((prev) => ({ ...prev, durationMinutes }))
}
accessibilityLabel="Duración"
>
{DURATIONS.map((m) => (
<Picker.Item key={m} label={`${m} minutos`} value={m} />
))}
</Picker>
</View>
<Pressable
style={styles.submit}
onPress={() => onSubmit(form)}
accessibilityRole="button"
>
<Text style={styles.submitText}>Reservar</Text>
</Pressable>
{iosPicker}
{androidPicker}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16, gap: 8 },
label: { fontSize: 14, fontWeight: "600", color: "#374151" },
field: {
padding: 14,
borderWidth: 1,
borderColor: "#d1d5db",
borderRadius: 8,
backgroundColor: "#fff",
},
pickerWrap: {
borderWidth: 1,
borderColor: "#d1d5db",
borderRadius: 8,
overflow: "hidden",
},
submit: {
marginTop: 12,
padding: 14,
borderRadius: 8,
backgroundColor: "#2563eb",
alignItems: "center",
},
submitText: { color: "#fff", fontWeight: "600" },
backdrop: { flex: 1, backgroundColor: "rgba(0,0,0,0.35)" },
sheet: {
backgroundColor: "#fff",
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
paddingBottom: 24,
},
sheetHeader: {
alignItems: "flex-end",
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: "#e5e7eb",
},
done: { color: "#2563eb", fontWeight: "600", fontSize: 16 },
});Lo que esto demuestra:
Pressable visibles abren pickers del SO en lugar de pedir a los usuarios que escriban fechas.Date en el estado de React; Intl.DateTimeFormat para mostrar.@react-native-picker/picker para una lista fija corta.@react-native-community/datetimepicker, que se conecta a UIDatePicker en iOS y DatePickerDialog / TimePickerDialog en Android.@react-native-picker/picker, que renderiza UIPickerView (iOS) o Spinner (Android) - no es un dropdown al estilo HTML.DateTimePicker en Android típicamente se muestra una vez y se descarta en la selección o cancelación; controlas la visibilidad con el estado local.Modal / hoja inferior para un patrón de disparador compacto.| Plataforma | mode | display Común | UX |
|---|---|---|---|
| iOS | date | spinner, inline, compact | Calendario en línea (iOS 14+) o rueda |
| iOS | time | spinner, compact | Rueda o chip de reloj compacto |
| Android | date | default, calendar, spinner | Diálogo del sistema |
| Android | time | default, clock, spinner | Diálogo del sistema |
En Android, prefiere default a menos que el diseño requiera una variante específica. En iOS, compact funciona bien en formularios densos; spinner dentro de una hoja es familiar para flujos de reserva.
// Almacena objetos Date nativos en el estado del formulario
const [startsAt, setStartsAt] = useState(new Date());
// Serializa solo en el límite de la API
const payload = {
startsAt: startsAt.toISOString(), // ISO-8601 UTC
dateOnly: startsAt.toISOString().slice(0, 10), // YYYY-MM-DD cuando la hora no es relevante
};
// Analiza respuestas de API de vuelta a Date
const restored = new Date(payload.startsAt);Date local; los servidores generalmente quieren UTC o una cadena de zona IANA.YYYY-MM-DD dedicada para evitar errores de desviación en zonas.import type { DateTimePickerEvent } from "@react-native-community/datetimepicker";
type PickerField = "date" | "time";
function handleChange(
field: PickerField,
event: DateTimePickerEvent,
selected?: Date,
) {
// event.type: "set" | "dismissed" (Android)
if (event.type === "dismissed") return;
if (selected) {
// actualizaciones específicas de campo estrecho
}
}DateTimePickerEvent.type distingue confirmar vs descartar en Android - no actualices el estado en dismissed.value de Picker.Item puede ser string | number; mantén tipos consistentes con tu esquema de formulario.useDatePickerField) para que las pantallas permanezcan legibles.Escribir fechas en TextInput - Los usuarios escriben 01/02/03 y los analizadores no están de acuerdo sobre mes vs día. Solución: Usa un picker nativo u hoja de cama; reserva campos de texto para búsqueda, no entrada de calendario.
Picker de Android dejado abierto - Olvidar configurar showPicker a false después de onChange deja un modal invisible bloqueando toques. Solución: Cierra en cada onChange en Android; protege con event.type === "dismissed".
Spinner de iOS sin Hecho - Los spinners en línea de iOS se actualizan en cada tick de rueda, lo que puede disparar validación a mitad de gesto. Solución: Usa una hoja con botones Hecho/Cancelar; confirma el valor solo en Hecho.
Picker como dropdown oculto - @react-native-picker/picker siempre ocupa altura de layout - no es un menú contraído. Solución: Usa un patrón de disparador Pressable + picker modal, o una hoja inferior del sistema de diseño, para listas largas.
Almacenar strings formateados en estado - Guardar "Mar 8, 2026" hace que comparaciones y mapeo de API sean frágiles. Solución: Almacena Date o strings ISO; formatea en tiempo de renderizado.
Sorpresas de zona horaria en envío - toISOString() convierte a UTC y puede cambiar el día del calendario. Solución: Envía valores normalizados date-fns-tz / Temporal (cuando esté disponible), o strings solo de fecha para cumpleaños.
Disparadores de accesibilidad faltante - Botones de calendario solo de ícono se anuncian como "botón" sin contexto. Solución: Establece accessibilityLabel incluyendo el valor actual; agrega accessibilityHint para la acción.
Listas de Picker enormes - Renderizar 200 filas <Picker.Item> daña el tiempo de montaje y el rendimiento de desplazamiento. Solución: Picker nativo para < ~20 elementos; lista modal con búsqueda (FlatList + filtro) para países o aeropuertos.
| Alternativa | Usarlo Cuando | No lo Hagas Cuando |
|---|---|---|
@react-native-community/datetimepicker | Fecha/hora estándar; quieres locale del SO, accesibilidad, y UX seguro para revisión de tienda | Necesitas ruedas pixel-idénticas en iOS y Android |
@react-native-picker/picker | Enumeraciones cortas fijas (duración, salutación, estado de EE.UU.) | Listas largas con búsqueda o multi-select |
Rueda personalizada (react-native-wheely, Reanimated) | Visual mandatado por marca entre plataformas | Puedes enviar pickers nativos - el costo de a11y y locale es alto |
Hoja inferior + FlatList | 50+ opciones, búsqueda, etiquetas de multi-select | Enums de tres opciones donde un picker nativo es más rápido de enviar |
TextInput + máscara (inputMode, regex) | Moneda, teléfono, códigos postales - no datos de calendario | Fechas, horas, o campos de política enumerados |
expo-document-picker / expo-image-picker | Archivos y medios - familia "input nativo" diferente | Campos de formulario escalares |
npx expo install @react-native-community/datetimepicker @react-native-picker/picker para que las versiones coincidan con RN 0.86.DatePickerDialog, iOS UIDatePicker.display="compact" en iOS 14+ produce un chip pulsable que se expande en línea - bueno para pantallas de configuración densa.mode="date" y mode="time", luego fusiona en una Date antes de enviar.refine de Zod en el valor fusionado.datetimepicker es para valores de reloj de calendario - devuelve una Date a través de onChange.Picker es para elegir entre opciones discretas de string/número - piensa duración, categoría, prefijo.Picker para fechas; no uses datetimepicker para listas de enum.@react-native-community/datetimepicker soporta props minimumDate y maximumDate en ambas plataformas.Controller cuyo render envuelve tu disparador + picker; llama a field.onChange(date) cuando el usuario confirma.value={field.value ?? new Date()} y valida con z.coerce.date() o z.date() de Zod.Controller que evitan re-renderizados.DateTimePicker con display="inline" directamente en el formulario.Intl.DateTimeFormat(undefined, options) - locale undefined sigue la configuración del dispositivo.surface importa.ScrollView pueden causar conflictos de gesto - anida cuidadosamente o usa presentación modal.@react-native-community/datetimepicker a un Pressable que llama a onChange con una fecha fija.Picker como View que expone onValueChange vía testID.accessibilityLabel de disparador y string ISO enviado - no semántica de rueda nativa.TextInput controlado, orden de enfoque, e inputModeController con re-renderizados mínimosz.date(), rangos, y copia de error en líneaPressable para disparadores de pickerVersiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0, y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026