Navigation, native modules and Expo
Routing with React Navigation, linking native code, and when the Expo managed workflow is the right trade-off.
Navigation
The community standard is React Navigation. A native stack uses the platform's own navigation controller, so transitions, gestures and accessibility behave like a system app.
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Stack = createNativeStackNavigator();
const Tabs = createBottomTabNavigator();
function NotesStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Notes" component={NotesScreen} />
<Stack.Screen name="NoteDetail" component={NoteDetailScreen} options={{ title: 'Detail' }} />
</Stack.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<Tabs.Navigator>
<Tabs.Screen name="Home" component={NotesStack} />
<Tabs.Screen name="Settings" component={SettingsScreen} />
</Tabs.Navigator>
</NavigationContainer>
);
}
// inside a screen
navigation.navigate('NoteDetail', { id: note.id });- Parameters arrive as
route.params; keep them small and serialisable, and load full records from a store or an API by id. - Typed routes (TypeScript
RootStackParamList) catch a wrong route name or a missing parameter at compile time. - Deep linking is configured in the container's linking config, plus an intent filter on Android and a URL scheme on iOS.
Native modules
Anything the core components do not cover — Bluetooth, secure storage, payments — needs a native module. Since React Native 0.74+ the recommended approach is a TurboModule with codegen, which gives a typed, lazily-loaded bridge.
# most packages are autolinked: install, then rebuild the native app
npm install react-native-mmkv
npx pod-install ios
npx react-native run-android
# permissions still have to be declared natively
# android/app/src/main/AndroidManifest.xml
# <uses-permission android:name="android.permission.CAMERA" />
# ios/Notes/Info.plist
# <key>NSCameraUsageDescription</key><string>Scan receipts</string>⚠️
Adding a native module means rebuilding the app binary — a JavaScript reload is not enough, and a hot update service cannot ship new native code. Plan native dependencies before a release, not during one.
The Expo trade-off
| Aspect | Expo managed | Community CLI |
|---|---|---|
| Native project on disk | Generated, managed by the tool | Yours from the start |
| Adding native code | Config plugins or a dev build | Edit the native project directly |
| Over-the-air updates | Built in with EAS Update | Possible but you wire it yourself |
| CI and signing | EAS Build handles both platforms | You configure Gradle, Xcode and CI |
| Upgrade cost | Mostly a dependency bump | Manual native upgrade steps |
- Expo Go is for prototyping only: it cannot run a custom native module, so any project that needs one moves to a development build.
npx expo prebuildgenerates the native projects so you can inspect or modify them; after that you are on a bare workflow with Expo's libraries.- EAS Build produces signed Android and iOS artefacts from a config file, which removes the need for a Mac in the loop.
FAQ
React Native or a fully native app?
React Native wins when the UI is conventional, the team already knows React and both platforms must ship together. Go native when you need deep platform integration, heavy graphics or the very best frame timings.
Can I use web libraries?
Only if they do not touch the DOM. Anything importing
document, window or a browser-only API will fail; look for a React Native equivalent such as AsyncStorage or MMKV instead of localStorage.Related
Core components and styling Setup and your first screen
Last refreshed 2026-09-18.