Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 8 min read

React Native Date Picker: The Cross-Platform Setup

Picking a date sounds like a solved problem, and then you ship it: the Android dialog will not close, the iOS picker eats half the screen, and a user in Los Angeles reports that every booking is saved a day early. All three come from the same root cause — the two platforms disagree about what a date picker is, and JavaScript disagrees with both about what a date is. This guide covers the library choice, one wrapper that behaves the same on both platforms, and the storage rule that prevents the off-by-one-day bug.

The four options

LibraryUIExpo GoBest forTrade-off
@react-native-community/datetimepickerReal native controls✅ YesAlmost every appDifferent API per platform
react-native-date-pickerIts own modal wheel❌ Dev buildIdentical look on both platformsExtra native dependency
react-native-calendarsA month grid you style✅ YesRanges, availability, marked daysYou own a11y and localisation
Three text inputsWhatever you build✅ YesBirthdays far in the pastValidation and locale order (DD/MM vs MM/DD)

Start with the community picker unless you have a specific reason not to. It gives you the control the user already knows how to operate, in their locale, with their date format and their accessibility settings, for free. “It does not match our design system” is a real reason to switch — but it costs you all four of those, so make the trade deliberately rather than because a wheel picker looked nicer in Figma.

One wrapper, both platforms

npx expo install @react-native-community/datetimepicker

The trap is that the same JSX means two different things. On Android, mounting the component opens a system dialog and the component unmounts itself when it closes. On iOS it renders an inline view that sits in your layout until you remove it. A wrapper that hides the difference:

import { useState } from 'react';
import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';

export function DateField({ label, value, onChange, minimumDate, maximumDate }) {
  const [open, setOpen] = useState(false);

  function handleChange(event, selected) {
    // Android: the dialog is already closing — always drop our own flag
    if (Platform.OS === 'android') setOpen(false);
    if (event.type === 'dismissed') return;      // user hit Cancel
    if (selected) onChange(selected);
  }

  return (
    <View style={styles.field}>
      <Text style={styles.label}>{label}</Text>

      <Pressable style={styles.trigger} onPress={() => setOpen(true)}>
        <Text style={styles.value}>
          {value ? value.toLocaleDateString() : 'Select a date'}
        </Text>
      </Pressable>

      {open && (
        <DateTimePicker
          value={value ?? new Date()}
          mode="date"
          display={Platform.OS === 'ios' ? 'inline' : 'default'}
          minimumDate={minimumDate}
          maximumDate={maximumDate}
          onChange={handleChange}
        />
      )}

      {/* iOS keeps the picker mounted, so give it an explicit way out */}
      {open && Platform.OS === 'ios' && (
        <Pressable onPress={() => setOpen(false)}>
          <Text style={styles.done}>Done</Text>
        </Pressable>
      )}
    </View>
  );
}

toLocaleDateString() in the trigger is deliberate. A hardcoded MM/DD/YYYYis read as day-first by most of the world, so “03/04” means two different days depending on who is holding the phone. Let the platform format it.

For a time, pass mode="time". Android has no combined mode, so a date-and-time value means opening the date picker and then the time picker in sequence from its onChange — which is exactly why keeping this in one wrapper component is worth it.

The bug that reaches production: one day off

This one survives review because it is invisible to anyone developing in UTC or ahead of it. The picker gives you local midnight. toISOString() converts to UTC. In any timezone behind UTC, that lands on the previous day:

// user in Los Angeles picks 4 March
const picked = new Date(2026, 2, 4);        // 2026-03-04T00:00 local
picked.toISOString();                        // "2026-03-03T08:00:00.000Z"  ← 3rd
picked.toISOString().split('T')[0];          // "2026-03-03"  ← saved wrong

// store a calendar day as a calendar day
function toDateKey(d) {
  const pad = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
toDateKey(picked);                           // "2026-03-04"  ← correct

The rule underneath it: decide whether the field is a calendar day or a moment in time, and store it accordingly. A birthday, a due date, or a habit check-in is a calendar day — store "2026-03-04"and never attach a timezone to it. An appointment at 14:00, a message timestamp, or a reminder is a moment — store a full ISO string in UTC and render it in the viewer’s local time.

Mixing the two is what produces streaks that break at 11pm, reminders that fire an hour early after the clocks change, and bookings that land on the wrong day for exactly the users who are furthest from your office.

Ranges, and constraining at the picker

The community picker has no range mode. Two fields, with the second constrained by the first, covers most of what people actually need:

const [start, setStart] = useState(null);
const [end, setEnd] = useState(null);

<DateField label="Check in"  value={start} onChange={setStart} minimumDate={new Date()} />
<DateField label="Check out" value={end}   onChange={setEnd}   minimumDate={start ?? new Date()} />

Constraining with minimumDate beats validating on submit, because the user never gets to make the mistake. The same applies to a date of birth — maximumDate set to today removes an entire error message from your form. See the form validation guide for where the remaining checks belong.

When the range is the interface — availability calendars, booking grids, anything with marked and blocked days — switch to react-native-calendars and its period marking. That is a different component with a different job, not a bigger date picker.

Skip the boilerplate

Every app with a booking flow, a reminder, or a tracker writes this same wrapper — the platform branch, the dismissed event, the date-key formatter. It is not hard, it is just the kind of thing that is wrong in production for six months before someone in a negative UTC offset files a bug.

Describe the screen instead — “a booking form with a service picker, a date and time field, and a confirmation screen” — and ShipNative generates it as real React Native with the picker wiring in place, running on your phone in a couple of minutes. Export the project and the wrapper is yours to change.

Frequently Asked Questions

What is the standard date picker for React Native?

@react-native-community/datetimepicker. It renders the real UIDatePicker on iOS and the Material date and time dialogs on Android, so users get the control they already know. Install it with npx expo install @react-native-community/datetimepicker — it is supported in Expo Go, so no development build is needed.

Why does the Android date picker not close?

Because the two platforms use the component differently. On Android the picker is a modal dialog managed by the system, and it fires an event with type "dismissed" or "set" — you must set your visible state to false yourself in onChange. On iOS the picker is an inline view that stays mounted, so the same handler must not hide it on every change. This asymmetry is the single biggest source of date picker bugs.

Why is my selected date one day off?

Because a JavaScript Date is an instant in time, not a calendar day. The picker hands you local midnight; call toISOString() on it and you get the previous day in any timezone behind UTC. Store calendar dates as plain YYYY-MM-DD strings built from getFullYear, getMonth and getDate, and reserve full ISO timestamps for genuine moments like appointment times.

How do I show a date range picker in React Native?

The community picker selects a single value, so a range means either two pickers labelled Start and End with the end picker constrained by minimumDate, or a calendar component such as react-native-calendars that supports period marking. Two pickers is the smaller change and is usually enough for booking and filter screens.

Does the date picker work in Expo Go?

Yes. @react-native-community/datetimepicker is included in the Expo SDK, so install it with expo install and it runs in Expo Go. Alternatives that ship their own native views, such as react-native-date-picker, need a development build instead.

How do I make the picker match my app design?

Only partly, and that is the trade-off. The native pickers accept a themeVariant and accentColor on iOS, but you cannot restyle the wheel or the Material dialog. If the picker must match a custom design system, use a calendar library you control such as react-native-calendars, and accept that you are now maintaining the accessibility and localisation the native control gave you for free.

→

React Native Form Validation

Dates are the field most likely to pass validation and still be wrong.

Read guide →
→

Build a Booking App in Under an Hour

Where date and time selection is the whole product.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.