Skip to main content

Command Palette

Search for a command to run...

Designing a Type-Safe, Reusable Pill Selector in React Native

Updated
3 min readView as Markdown
H

I am an aspiring web developer. Transitioning to tech from electrical engineering background. Learning everyday and sharing my journey.

When designing mobile experiences, components like pill selectors (those horizontally scrollable button sets) are a staple — especially in apps with filtering, tab-like controls, or category selection.

In this post, I’ll walk you through how I approached building a type-safe, reusable, and minimal pill selector in React Native, using plain components and TypeScript.

👉 View the live interactive demo on Expo Snack

🧠 Design Principles Behind the Code

This component was designed around a few core principles that make it scalable, robust, and pleasant to work with in real apps.

✅ 1. Type-Safe Pills with Generics

By using TypeScript generics, the pill titles are strictly typed. This ensures:

  • You can only select from valid pill values.

  • onSelect can't be passed an invalid string.

  • Better autocompletion and compile-time safety.

This is especially useful in real apps where pill titles may also be used to drive filters or API queries.

✅ 1.1 Why Use as const When Passing Pills? (***MOST IMPORTANT***)

To fully leverage TypeScript’s type inference and make the pill titles truly literal and read-only, you should declare your pills array with as const:

const PILL_OPTIONS = [
  { title: 'All' },
  { title: 'Stocks' },
  { title: 'Crypto' },
] as const;

This does two things:

  • Literal Types: Without as const, TypeScript widens the string type to string. With as const, it keeps the exact literal type ('All' | 'Stocks' | 'Crypto'), enabling precise type safety.

  • Readonly: It marks the array and objects as immutable, preventing accidental mutation which could lead to bugs.

This literal typing is what allows the generic type T extends string to properly narrow the accepted values in PillSelectorList and the usePillSelector hook, enforcing you only select valid pills.

Without as const, TypeScript would lose this precision and fall back to string, defeating the purpose of the type safety layer.

✅ 2. Separation of Concerns

The code is cleanly split into:

  • PillButton: handles the rendering and styling of individual pills.

  • PillSelectorList: handles the list layout and selection propagation.

  • usePillSelector: a hook to manage local selection state.

This means:

  • You can reuse PillSelectorList anywhere in the app.

  • You can swap usePillSelector with a global state hook or Redux logic.

  • You can test logic independently from the UI.

✅ 3. Minimal Dependencies

The entire UI is built using only React Native primitives (FlatList, Pressable, StyleSheet), which makes it:

  • Portable across platforms (iOS, Android, web).

  • Lightweight, with zero third-party dependencies.

  • Easy to customize visually.

✅ 4. Memoization for Performance

Using React.memo and useCallback, the component avoids unnecessary re-renders — even if the parent re-renders. This small touch matters in lists and scroll views where performance can degrade quickly.

✅ 5. Declarative Usage

The consumer API is as clean as:

const PILL_OPTIONS = [
  { title: 'All' },
  { title: 'Stocks' },
  { title: 'Mutual Funds' },
  { title: 'Gold' },
  { title: 'Crypto' },
  { title: 'Currency'}
] as const;

export default function App () {
  const { selected, onSelect } = usePillSelector<typeof PILL_OPTIONS>('All');

  return (
    <View>
      <PillSelectorList
        pills={PILL_OPTIONS}
        selected={selected}
        onSelect={onSelect}
      />
      <Text>
        Selected: {selected}
      </Text>
    </View>
  );
};

You don’t need to think about key management, styling logic, or conditionals — it’s abstracted cleanly.

🧪 Try It Live

Want to see how it works? Play with it here:
👉 Expo Snack Demo

You can fork it, customize the pills, or integrate it into your own project.

🎯 Final Thoughts

This is a small component, but the way it’s structured reflects the kind of thinking that leads to scalable, maintainable, and type-safe UI development.

If you're building production apps and want to avoid repetitive boilerplate and bugs, these patterns can help make your components not just reusable, but delightful to work with.