Building Reusable React Components Across Web and Mobile
TIL sharing components across a React web app and a React Native app only works if you split presentation from platform primitives from day one — retrofitting it later means rewriting most of your UI layer.
The Problem #
Pasal needed the same core UI — inventory lists, appointment cards, status badges — on both the web dashboard and the React Native app. Building each twice meant every product change had to be implemented, tested, and fixed in two places.
Context #
Early on I treated the web and mobile UIs as separate codebases that happened to solve the same problem, because that's how the project started: web first, mobile bolted on later once the business needed a driver/staff app.
What I Tried #
I started extracting "shared" components by copy-pasting the web version into the RN project and swapping div/span for View/Text, assuming the logic could stay identical.
What Went Wrong #
The components weren't actually shareable — they had DOM-specific styling (CSS classes, :hover states, box-shadow) baked into the same file as the actual list-rendering and state logic. Every "shared" component still needed a near-total rewrite per platform, which defeated the point.
The Solution #
Split every component into a platform-agnostic logic layer (data shape, derived state, event handlers) and a platform-specific presentation layer. The logic layer became plain functions/hooks with no JSX at all; each platform got its own thin presentational component that just consumed that hook.
// Shared: platform-agnostic
function useInventoryItem(item) {
const isLowStock = item.quantity <= item.reorderThreshold;
return { isLowStock, label: item.quantity <= 0 ? "Out of stock" : `${item.quantity} left` };
}
// Platform-specific: web
function InventoryBadge({ item }) {
const { isLowStock, label } = useInventoryItem(item);
return <span className={isLowStock ? "text-red-500" : "text-green-500"}>{label}</span>;
}Why It Works #
Web and React Native share the JavaScript runtime and component model, but not the rendering primitives or styling system. Anything that touches div/View or CSS/StyleSheet is inherently platform-specific; anything that's pure data/logic isn't. Drawing that line explicitly is what makes sharing actually pay off.
Lessons Learned #
"Reusable component" doesn't mean "one file that runs everywhere" in a React + React Native codebase — it means one piece of logic, two thin renderers.
What I Would Do Differently #
I'd design the logic/presentation split from the first component instead of discovering it after several rounds of copy-paste-and-rewrite.
Related Concepts #
Custom hooks, headless component patterns, platform-specific file extensions (.web.tsx / .native.tsx).
Related content
Designing a Delivery App for Unreliable Networks, Not Just Offline
TIL that 'handle offline' undersold the actual problem — the failure mode that hurt drivers most was a flaky, half-connected state, not a clean offline/online boundary.
Designing a Dispatch Management Dashboard for Real Operations, Not a Generic Admin Panel
TIL that a dispatch dashboard built around 'show all the order fields' overwhelmed the person actually using it — the redesign that worked started from what a dispatcher needs to decide, not what data exists.
Pasal Business Management Platform
Internal business applications developed for inventory management, appointment systems, and daily operational workflows.