DEV Community

Cover image for I replaced six React Native picker libraries with one, and it has zero native code
Eliyahu Kriel
Eliyahu Kriel

Posted on Edited on AI-assisted

I replaced six React Native picker libraries with one, and it has zero native code

Every React Native app I've worked on ended up with the same pile of packages:

  • @react-native-community/datetimepicker for dates and times
  • react-native-calendars because the date picker can't do ranges
  • a dropdown library, because the native picker can't search
  • a country picker, then a phone input on top of it
  • an emoji keyboard for the chat screen
  • a bottom sheet to put half of these in

Six APIs, six looks, some of them native modules that need a rebuild, and most of them broken or missing on the web.

So I built React Native Pickers: one package with all of them, written in plain JavaScript on React Native's own APIs. No native code, no dependencies.

React Native Pickers screens in light and dark: a date range with prices on iOS, the Android clock dial, the iOS emoji picker, Android color swatches and an iOS phone number field

What's in it

  • Wheels: the 3D iOS wheel, with looping, disabled rows and tap-to-type
  • Dates: a calendar (single day, range, multiple days, several ranges) or day / month / year wheels; a month picker; a week strip
  • Time: wheels, the Material clock dial, or columns to click; durations
  • Date and time together
  • Select: search, multi-select with chips, groups, icons and server search
  • Country: about 250 countries with flag emoji, dialing codes and names in the user's language
  • Phone number: a country code button, formatting as you type, an E.164 value
  • Emoji: 1,923 emojis with search, skin tones and recently used
  • Color: iOS's grid / spectrum / sliders, Google's swatch circles, or the browser's spectrum
  • Segmented control and chips
  • Bottom sheets and form fields to put any of them in

What using it looks like

import { useState } from 'react';
import { View } from 'react-native';
import { DatePickerField, SelectPicker, TimePickerField } from '@elik56a/react-native-pickers';
import type { DateRange } from '@elik56a/react-native-pickers';

const ROOMS = [
  { label: 'Single', value: 'single' },
  { label: 'Double', value: 'double' },
  { label: 'Suite', value: 'suite' },
];

export default function Booking() {
  const [stay, setStay] = useState<DateRange>({ start: null, end: null });
  const [arrival, setArrival] = useState<Date | undefined>();
  const [room, setRoom] = useState<string | null>(null);

  return (
    <View style={{ gap: 16, padding: 16 }}>
      <DatePickerField label="Stay" mode="range" value={stay} onConfirm={setStay} minDate={new Date()} />
      <TimePickerField label="Arrival" value={arrival} onConfirm={setArrival} minuteInterval={15} />
      <SelectPicker label="Room" options={ROOMS} value={room} onChange={setRoom} />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Each field opens its picker in a bottom sheet on a phone and a dropdown in the browser, and shows the chosen value formatted for the user's locale. Every component is controlled the same way: value and onChange (or onConfirm for the sheets), and onChange only fires when the user changes something.

It looks native on each platform

This is the part I cared about most. By default each picker takes the design of the platform it runs on:

  • iOS: wheels, a tinted selected day, Cancel and Done in the sheet header
  • Android: Material Design, with the clock dial, outlined fields with floating labels, and OK at the bottom
  • Web: a compact calendar, time columns to click, dropdowns, and a focus ring

If your app has its own look and you want the same pickers everywhere, it's one line:

<PickersProvider design="ios">
  <App />
</PickersProvider>
Enter fullscreen mode Exit fullscreen mode

Dark mode follows the system, and every color, radius and font comes from theme tokens you can override. Every visual part also has a style slot, and render props let you replace a day cell, an option row or the whole field.

Why zero native code matters

Everything is built on React Native's core APIs: Animated with the native driver, PanResponder, FlatList and Modal. No Reanimated, no Gesture Handler, no SVG. That means:

  • it works in Expo Go, with no development build and no config plugin
  • it ships in over-the-air updates, because there's nothing native to rebuild
  • it works on React Native Web, which is where most picker libraries fall short
  • it doesn't break when your React Native version changes

Things I didn't want to install separately

A few details that usually mean another package:

  • Prices and dots on calendar days. markedDates takes a label and up to three colored dots per day, so a booking calendar can show $129 under each night.
  • Server search. Give SelectPicker a loadOptions(query) that returns a promise; it debounces, shows a loader and ignores stale answers.
  • Phone numbers without libphonenumber. PhoneField groups digits the way about 50 countries write them, drops the leading 0 people type at home, and reads the country from a pasted +44… number. The value is always E.164, like +447911123456.
  • Accessibility. Wheels are adjustable for VoiceOver and TalkBack, days announce their full date, and on the web you can use every picker from the keyboard.

Coming from another library?

There's a prop-by-prop migration guide for 12 libraries: @react-native-community/datetimepicker, react-native-date-picker, react-native-modal-datetime-picker, react-native-calendars, @react-native-picker/picker, react-native-picker-select, react-native-dropdown-picker, react-native-element-dropdown, react-native-country-picker-modal, react-native-phone-number-input, rn-emoji-keyboard and @react-native-segmented-control/segmented-control. Most of them come down to renaming a few props.

Try it

npx expo install @elik56a/react-native-pickers
Enter fullscreen mode Exit fullscreen mode

It's MIT licensed and I'm working on it actively. I'd really like to hear what's missing, what breaks in your app, and what you'd need before using it in production. Comments here or issues on GitHub both work, and a star helps other people find it.

Top comments (0)