We shipped a React Native app to the App Store and Google Play in January 2016 and have been maintaining and extending it through November 2017. The app is a service booking platform with real-time chat, maps integration, and payment flow. Here's an honest retrospective.
The Promise vs The Reality
The pitch: "Learn once, write anywhere." One codebase for iOS and Android, JavaScript developers can build mobile apps, faster time to market.
The reality: 75-80% shared code, 20-25% platform-specific. JavaScript developers can build mobile apps, but mobile development knowledge still matters. Faster time to market - genuinely true, especially for business logic.
What Worked Extremely Well
Business Logic and Data Layer
Redux store, API calls, data transformations, business rules - 100% shared. This is where React Native saves the most time. Any bug fix in the booking flow logic is fixed once and ships to both platforms.
Navigation (React Navigation)
By late 2016, React Navigation had matured enough for production use. Tab navigation, stack navigation, modal screens - all handled with JavaScript. The platform-specific transitions (iOS swipe-back, Android back button) are handled automatically.
Forms and Data Input
Standard text inputs, selects, date pickers - mostly fine across platforms. Platform-specific styling needed, but the logic is shared.
The JavaScript Bridge: Where Performance Hurt
React Native's architecture in 2017: JavaScript thread ↔ Bridge ↔ Native thread. Every interaction that crosses the bridge has latency.
This hit us in two places:
1. Gesture-driven animations:
// This looked fine in development, choppy on mid-range Android in production
// Because: JS calculates position → Bridge → Native renders
class SwipeableCard extends Component {
handleGestureEvent(event) {
this.setState({
translateX: event.nativeEvent.translationX
});
// setState triggers JS re-render → Bridge → Native
// At 60 FPS you have 16ms. Bridge adds 4-8ms. Math doesn't work.
}
}
Fix: Use Animated.event with useNativeDriver: true - runs animation entirely on the native thread, never touches the bridge:
const translateX = new Animated.Value(0);
// This runs on native thread - no bridge crossing per frame
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: translateX } }],
{ useNativeDriver: true } // The magic flag
);
2. Long lists:
ScrollView renders all children at once. With 200+ list items, this destroyed performance. The fix was FlatList (virtualizing list rendering - only renders visible items + a buffer):
// Bad for long lists
<ScrollView>
{items.map(item => <BookingCard key={item.id} {...item} />)}
</ScrollView>
// Good: only renders ~15-20 items at a time regardless of list size
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => <BookingCard {...item} />}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
removeClippedSubviews={true}
/>
When We Had to Write Native Code
Three modules required dropping into native:
1. Background location (iOS): React Native's location API in 2017 didn't support proper iOS background location for our use case (tracking courier positions). We wrote an Objective-C module.
2. Custom camera with overlay: The standard Camera component couldn't support our overlay UI. We built a native camera module in both Swift and Kotlin and bridged it.
3. Biometric authentication: TouchID on iOS required a native module. React Native Touch ID libraries existed but had reliability issues we couldn't ship with.
The native bridging API is well-documented but requires knowing both iOS and Android native development. The "no mobile knowledge needed" promise breaks here.
The 60 FPS Rule
A fluid mobile UI runs at 60 frames per second - 16.67ms per frame. Any JS computation that takes longer blocks the frame.
Profiling with the React Native Performance Monitor showed us the culprits:
JSON.parse()on large API responses in the render cycle- Deep component trees re-rendering on every state change
- Heavy computation in
render()functions
Fixes:
- Parse JSON off the main thread (web workers equivalent doesn't exist in RN - we moved parsing to the API layer before Redux dispatch)
shouldComponentUpdate(orPureComponent) to prevent unnecessary re-renders- Move computed values out of
render()and memoize them
Platform Divergence in Practice
Code that worked identically on iOS sometimes needed Android-specific handling:
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
header: {
paddingTop: Platform.OS === 'ios' ? 20 : 0, // iOS status bar
// Android handles this differently
},
shadow: {
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
},
android: {
elevation: 4, // Android uses elevation, not shadow properties
},
}),
},
});
Text rendering differences, keyboard behavior, back button handling, permission request dialogs - all required platform-specific handling at some point.
The Verdict After 14 Months
Good fit for React Native:
- Apps with heavy business logic (shared across platforms)
- Startup MVPs where time-to-market matters
- Teams already proficient in React
- Apps with mostly standard UI (lists, forms, navigation)
Not a good fit:
- Apps requiring heavy animations or gesture-driven UIs
- Games or graphically intensive apps
- Apps needing deep hardware access
- Teams with no mobile development background at all
We shipped a production app to 12,000 users across iOS and Android with a team of two React developers. That wouldn't have been possible with pure native development. The tradeoffs were real, but so was the velocity.
In 2024, React Native's architecture has been rebuilt (the new architecture with JSI replacing the bridge) which addresses many of the performance issues we hit. Flutter also emerged as a strong alternative. The cross-platform space is better now than it was in 2017 - but the fundamental tradeoffs remain.
Aunimeda develops mobile applications for iOS and Android - from MVP to production-ready apps with full backend integration.
Contact us to discuss your mobile project. See also: Mobile App Development, Mobile Game Development