Navigation in React Native
React Navigation is the most popular navigation library for React Native. It provides native navigation experiences with Stack, Tab, and Drawer navigators.
Installation
# Install React Navigation core
npm install @react-navigation/native
# Install dependencies for Expo
npx expo install react-native-screens react-native-safe-area-context
# Install navigators you need
npm install @react-navigation/native-stack # Stack navigation
npm install @react-navigation/bottom-tabs # Tab navigation
npm install @react-navigation/drawer # Drawer navigation
# For React Native CLI, also run:
cd ios && pod install
Basic Setup
// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';
// Type-safe navigation
type RootStackParamList = {
Home: undefined;
Details: { itemId: number; title: string };
};
const Stack = createNativeStackNavigator<RootStackParamList>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My App' }}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route }) => ({
title: route.params.title
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Stack Navigation
// screens/HomeScreen.tsx
import { View, Text, Button } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
function HomeScreen({ navigation }: Props) {
return (
<View style={{ flex: 1, padding: 20 }}>
<Text>Home Screen</Text>
{/* Navigate to Details */}
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details', {
itemId: 42,
title: 'Item Details',
})}
/>
{/* Push adds to stack (can go to same screen again) */}
<Button
title="Push Details"
onPress={() => navigation.push('Details', { itemId: 1 })}
/>
</View>
);
}
// screens/DetailsScreen.tsx
function DetailsScreen({ route, navigation }: Props) {
const { itemId, title } = route.params;
return (
<View style={{ flex: 1, padding: 20 }}>
<Text>Details for item: {itemId}</Text>
{/* Go back */}
<Button title="Go Back" onPress={() => navigation.goBack()} />
{/* Go to first screen in stack */}
<Button title="Go to Home" onPress={() => navigation.popToTop()} />
{/* Replace current screen */}
<Button
title="Replace"
onPress={() => navigation.replace('Details', { itemId: 99 })}
/>
</View>
);
}
Tab Navigation
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
type TabParamList = {
Home: undefined;
Search: undefined;
Profile: undefined;
};
const Tab = createBottomTabNavigator<TabParamList>();
function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName: string;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Search') {
iconName = focused ? 'search' : 'search-outline';
} else if (route.name === 'Profile') {
iconName = focused ? 'person' : 'person-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray',
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarBadge: 3, // Show badge
}}
/>
</Tab.Navigator>
);
}
Drawer Navigation
import { createDrawerNavigator } from '@react-navigation/drawer';
import { DrawerContentScrollView, DrawerItem } from '@react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator
screenOptions={{
drawerStyle: {
backgroundColor: '#fff',
width: 280,
},
drawerActiveTintColor: '#007AFF',
}}
drawerContent={(props) => <CustomDrawerContent {...props} />}
>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
</Drawer.Navigator>
);
}
// Custom drawer content
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
{/* User profile header */}
<View style={styles.header}>
<Image source={{ uri: 'avatar.jpg' }} style={styles.avatar} />
<Text style={styles.username}>John Doe</Text>
</View>
{/* Drawer items */}
<DrawerItem
label="Home"
onPress={() => props.navigation.navigate('Home')}
/>
<DrawerItem
label="Logout"
onPress={() => handleLogout()}
/>
</DrawerContentScrollView>
);
}
Nested Navigators
// Common pattern: Tabs with Stacks inside
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
{/* Main tabs */}
<Stack.Screen
name="MainTabs"
component={TabNavigator}
options={{ headerShown: false }}
/>
{/* Modal screens on top of tabs */}
<Stack.Screen
name="Modal"
component={ModalScreen}
options={{ presentation: 'modal' }}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
function TabNavigator() {
return (
<Tab.Navigator>
<Tab.Screen name="HomeTab" component={HomeStackNavigator} />
<Tab.Screen name="ProfileTab" component={ProfileStackNavigator} />
</Tab.Navigator>
);
}
// Each tab has its own stack
function HomeStackNavigator() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}
💡 Navigation Patterns
- • Auth Flow: Stack with conditional screens based on login state
- • Main App: Tab navigator as the main container
- • Detail Screens: Stack navigator inside each tab
- • Modals: Stack navigator with presentation: 'modal'
Screen Options & Headers
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{
// Title
title: 'Details',
headerTitle: (props) => <CustomTitle {...props} />,
// Header styling
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
// Header buttons
headerLeft: () => <BackButton />,
headerRight: () => <MenuButton />,
// Hide header
headerShown: false,
// Presentation style
presentation: 'modal', // 'card' | 'modal' | 'transparentModal'
animation: 'slide_from_right', // Animation type
// Gestures
gestureEnabled: true,
}}
/>
// Dynamic options from component
function DetailsScreen({ navigation, route }) {
useLayoutEffect(() => {
navigation.setOptions({
title: route.params.title,
headerRight: () => (
<Button title="Save" onPress={handleSave} />
),
});
}, [navigation, route.params.title]);
return <View>...</View>;
}
Deep Linking
// Configure deep links in NavigationContainer
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Details: 'details/:id',
Profile: {
path: 'user/:username',
parse: {
username: (username) => username.toLowerCase(),
},
},
NotFound: '*',
},
},
};
function App() {
return (
<NavigationContainer linking={linking} fallback={<LoadingScreen />}>
<Stack.Navigator>
{/* screens */}
</Stack.Navigator>
</NavigationContainer>
);
}
// URLs that work:
// myapp://home
// myapp://details/123
// https://myapp.com/user/johndoe
// app.json (Expo) - configure URL schemes
{
"expo": {
"scheme": "myapp",
"ios": {
"associatedDomains": ["applinks:myapp.com"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"data": { "scheme": "https", "host": "myapp.com" }
}
]
}
}
}
Auth Flow Pattern
function RootNavigator() {
const { user, isLoading } = useAuth();
if (isLoading) {
return <SplashScreen />;
}
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{user ? (
// User is signed in
<>
<Stack.Screen name="Main" component={MainTabs} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
// User is not signed in
<>
<Stack.Screen name="Welcome" component={WelcomeScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
</>
)}
</Stack.Navigator>
);
}
// Navigation will automatically handle transitions
// when user logs in/out