Designing a Type-Safe, Reusable Pill Selector in React Native
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.
onSelectcan'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 tostring. Withas 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
PillSelectorListanywhere in the app.You can swap
usePillSelectorwith 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.


