React Native Navigation
Use this skill when implementing navigation in React Native applications using React Navigation (the de facto standard navigation library).
Key Concepts
Installation
npm install @react-navigation/native npm install react-native-screens react-native-safe-area-context
For stack navigation
npm install @react-navigation/native-stack
For tab navigation
npm install @react-navigation/bottom-tabs
For drawer navigation
npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated
Basic Setup
import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
type RootStackParamList = { Home: undefined; Details: { itemId: string }; };
const Stack = createNativeStackNavigator<RootStackParamList>();
export default function App() { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> </NavigationContainer> ); }
Stack Navigation
The most common navigation pattern:
import React from 'react'; import { View, Text, Button } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack';
type RootStackParamList = { Home: undefined; Details: { itemId: string; title: string }; };
type HomeProps = NativeStackScreenProps<RootStackParamList, 'Home'>; type DetailsProps = NativeStackScreenProps<RootStackParamList, 'Details'>;
function HomeScreen({ navigation }: HomeProps) { return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>Home Screen</Text> <Button title="Go to Details" onPress={() => navigation.navigate('Details', { itemId: '123', title: 'My Item', }) } /> </View> ); }
function DetailsScreen({ route, navigation }: DetailsProps) { const { itemId, title } = route.params;
return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>Details Screen</Text> <Text>Item ID: {itemId}</Text> <Text>Title: {title}</Text> <Button title="Go Back" onPress={() => navigation.goBack()} /> </View> ); }
Tab Navigation
Bottom tabs for primary navigation:
import React from 'react'; 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>();
export default function TabNavigator() { return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ focused, color, size }) => { let iconName: keyof typeof Ionicons.glyphMap;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Search') {
iconName = focused ? 'search' : 'search-outline';
} else {
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} />
</Tab.Navigator>
); }
Best Practices
Type-Safe Navigation
Define navigation types for type safety:
import { NativeStackScreenProps } from '@react-navigation/native-stack';
// Define param list type RootStackParamList = { Home: undefined; Details: { itemId: string }; UserProfile: { userId: string; name: string }; };
// Declare global types declare global { namespace ReactNavigation { interface RootParamList extends RootStackParamList {} } }
// Use typed props type DetailsProps = NativeStackScreenProps<RootStackParamList, 'Details'>;
function DetailsScreen({ route, navigation }: DetailsProps) { // route.params is fully typed const { itemId } = route.params;
// navigation.navigate is type-safe navigation.navigate('UserProfile', { userId: '123', name: 'John', });
return <View />; }
Header Customization
Customize navigation headers:
<Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: '#007AFF', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }}
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'My Home', headerRight: () => ( <Button onPress={() => console.log('Pressed')} title="Info" color="#fff" /> ), }} /> </Stack.Navigator>
Dynamic Header Options
Set header options from screen:
import { useLayoutEffect } from 'react';
function DetailsScreen({ navigation, route }: DetailsProps) { useLayoutEffect(() => { navigation.setOptions({ title: route.params.title, headerRight: () => ( <Button title="Save" onPress={() => console.log('Save')} /> ), }); }, [navigation, route.params.title]);
return <View />; }
Nested Navigators
Combine different navigation patterns:
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
const HomeStack = createNativeStackNavigator(); const ProfileStack = createNativeStackNavigator(); const Tab = createBottomTabNavigator();
function HomeStackScreen() { return ( <HomeStack.Navigator> <HomeStack.Screen name="Home" component={HomeScreen} /> <HomeStack.Screen name="Details" component={DetailsScreen} /> </HomeStack.Navigator> ); }
function ProfileStackScreen() { return ( <ProfileStack.Navigator> <ProfileStack.Screen name="Profile" component={ProfileScreen} /> <ProfileStack.Screen name="Settings" component={SettingsScreen} /> </ProfileStack.Navigator> ); }
export default function App() { return ( <NavigationContainer> <Tab.Navigator> <Tab.Screen name="HomeTab" component={HomeStackScreen} /> <Tab.Screen name="ProfileTab" component={ProfileStackScreen} /> </Tab.Navigator> </NavigationContainer> ); }
Common Patterns
Authentication Flow
import React, { useState } from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
type RootStackParamList = { SignIn: undefined; SignUp: undefined; Home: undefined; Details: { itemId: string }; };
const Stack = createNativeStackNavigator<RootStackParamList>();
export default function App() { const [isSignedIn, setIsSignedIn] = useState(false);
return ( <NavigationContainer> <Stack.Navigator> {!isSignedIn ? ( // Auth screens <> <Stack.Screen name="SignIn" component={SignInScreen} /> <Stack.Screen name="SignUp" component={SignUpScreen} /> </> ) : ( // App screens <> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </> )} </Stack.Navigator> </NavigationContainer> ); }
Deep Linking
Configure deep linking:
import { NavigationContainer } from '@react-navigation/native';
const linking = { prefixes: ['myapp://', 'https://myapp.com'], config: { screens: { Home: 'home', Details: 'details/:itemId', UserProfile: 'user/:userId', }, }, };
export default function App() { return ( <NavigationContainer linking={linking}> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> <Stack.Screen name="UserProfile" component={UserProfileScreen} /> </Stack.Navigator> </NavigationContainer> ); }
Modal Navigation
Present screens as modals:
<Stack.Navigator> <Stack.Group> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Group> <Stack.Group screenOptions={{ presentation: 'modal' }}> <Stack.Screen name="CreatePost" component={CreatePostScreen} /> <Stack.Screen name="Settings" component={SettingsScreen} /> </Stack.Group> </Stack.Navigator>
Navigation Guards
Protect routes with guards:
import { useEffect } from 'react';
function ProtectedScreen({ navigation }: any) { const isAuthenticated = useAuth(); // Custom hook
useEffect(() => { if (!isAuthenticated) { navigation.replace('SignIn'); } }, [isAuthenticated, navigation]);
if (!isAuthenticated) { return null; // Or loading screen }
return <View>{/* Protected content */}</View>; }
Custom Tab Bar
Create custom tab bar:
import { View, Text, TouchableOpacity } from 'react-native';
function CustomTabBar({ state, descriptors, navigation }: any) { return ( <View style={{ flexDirection: 'row', height: 60 }}> {state.routes.map((route: any, index: number) => { const { options } = descriptors[route.key]; const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
return (
<TouchableOpacity
key={route.key}
onPress={onPress}
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: isFocused ? '#007AFF' : '#fff',
}}
>
<Text style={{ color: isFocused ? '#fff' : '#000' }}>
{options.title || route.name}
</Text>
</TouchableOpacity>
);
})}
</View>
); }
<Tab.Navigator tabBar={(props) => <CustomTabBar {...props} />}> <Tab.Screen name="Home" component={HomeScreen} /> <Tab.Screen name="Profile" component={ProfileScreen} /> </Tab.Navigator>
Anti-Patterns
Don't Navigate in useEffect Without Dependencies
// Bad - Infinite loop risk useEffect(() => { navigation.navigate('Home'); });
// Good - Proper dependencies useEffect(() => { if (shouldNavigate) { navigation.navigate('Home'); } }, [shouldNavigate, navigation]);
Don't Use navigate() for Replacing Screens
// Bad - Adds to navigation stack navigation.navigate('SignIn');
// Good - Replaces current screen navigation.replace('SignIn');
Don't Access Navigation Without Type Safety
// Bad - No type safety function MyScreen({ navigation }: any) { navigation.navigate('Detials', { itemId: 123 }); // Typo won't be caught }
// Good - Type-safe navigation type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
function MyScreen({ navigation }: Props) { navigation.navigate('Details', { itemId: '123' }); // Type-checked }
Don't Forget to Handle Back Button on Android
import { useEffect } from 'react'; import { BackHandler } from 'react-native';
function MyScreen({ navigation }: any) { useEffect(() => { const backHandler = BackHandler.addEventListener( 'hardwareBackPress', () => { navigation.goBack(); return true; // Prevent default behavior } );
return () => backHandler.remove();
}, [navigation]);
return <View />; }
Related Skills
-
react-native-components: Building UI components for screens
-
react-native-platform: Platform-specific navigation behavior
-
react-native-performance: Optimizing navigation performance