# React Navigation Patterns ## Setup and Configuration ### Installation ```bash # Core packages npm install @react-navigation/native npm install @react-navigation/native-stack npm install @react-navigation/bottom-tabs # Required peer dependencies npm install react-native-screens react-native-safe-area-context ``` ### Type-Safe Navigation Setup ```typescript // navigation/types.ts import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { BottomTabScreenProps } from "@react-navigation/bottom-tabs"; import { CompositeScreenProps, NavigatorScreenParams, } from "@react-navigation/native"; // Define param lists for each navigator export type RootStackParamList = { Main: NavigatorScreenParams; Modal: { title: string }; Auth: NavigatorScreenParams; }; export type MainTabParamList = { Home: undefined; Search: { query?: string }; Profile: { userId: string }; }; export type AuthStackParamList = { Login: undefined; Register: undefined; ForgotPassword: { email?: string }; }; // Screen props helpers export type RootStackScreenProps = NativeStackScreenProps; export type MainTabScreenProps = CompositeScreenProps< BottomTabScreenProps, RootStackScreenProps >; // Global type declaration declare global { namespace ReactNavigation { interface RootParamList extends RootStackParamList {} } } ``` ### Navigation Hooks ```typescript // hooks/useAppNavigation.ts import { useNavigation, useRoute, RouteProp } from "@react-navigation/native"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { RootStackParamList } from "./types"; export function useAppNavigation() { return useNavigation>(); } export function useTypedRoute() { return useRoute>(); } ``` ## Stack Navigation ### Basic Stack Navigator ```typescript import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { RootStackParamList } from './types'; const Stack = createNativeStackNavigator(); function RootNavigator() { return ( ); } ``` ### Screen with Dynamic Options ```typescript function DetailScreen({ route, navigation }: DetailScreenProps) { const { itemId } = route.params; const [item, setItem] = useState(null); useEffect(() => { // Update header when data loads if (item) { navigation.setOptions({ title: item.title, headerRight: () => ( shareItem(item)}> ), }); } }, [item, navigation]); // Prevent going back with unsaved changes useEffect(() => { const unsubscribe = navigation.addListener('beforeRemove', (e) => { if (!hasUnsavedChanges) return; e.preventDefault(); Alert.alert( 'Discard changes?', 'You have unsaved changes. Are you sure you want to leave?', [ { text: "Don't leave", style: 'cancel' }, { text: 'Discard', style: 'destructive', onPress: () => navigation.dispatch(e.data.action), }, ] ); }); return unsubscribe; }, [navigation, hasUnsavedChanges]); return {/* Content */}; } ``` ## Tab Navigation ### Bottom Tab Navigator ```typescript import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { MainTabParamList } from './types'; import { Ionicons } from '@expo/vector-icons'; const Tab = createBottomTabNavigator(); function MainTabNavigator() { return ( ({ tabBarIcon: ({ focused, color, size }) => { const icons: Record = { Home: focused ? 'home' : 'home-outline', Search: focused ? 'search' : 'search-outline', Profile: focused ? 'person' : 'person-outline', }; return ( ); }, tabBarActiveTintColor: '#6366f1', tabBarInactiveTintColor: '#9ca3af', tabBarStyle: { backgroundColor: '#ffffff', borderTopWidth: 1, borderTopColor: '#e5e7eb', paddingBottom: 8, paddingTop: 8, height: 60, }, tabBarLabelStyle: { fontSize: 12, fontWeight: '500', }, headerStyle: { backgroundColor: '#ffffff' }, headerTitleStyle: { fontWeight: '600' }, })} > ); } ``` ### Custom Tab Bar ```typescript import { View, Pressable, StyleSheet } from 'react-native'; import { BottomTabBarProps } from '@react-navigation/bottom-tabs'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated'; function CustomTabBar({ state, descriptors, navigation }: BottomTabBarProps) { return ( {state.routes.map((route, index) => { const { options } = descriptors[route.key]; const label = options.tabBarLabel ?? route.name; 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 ( ); })} ); } function TabBarButton({ label, isFocused, onPress, icon }) { const scale = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); return ( { scale.value = withSpring(0.9); }} onPressOut={() => { scale.value = withSpring(1); }} style={styles.tabButton} > {icon?.({ focused: isFocused, color: isFocused ? '#6366f1' : '#9ca3af', size: 24, })} {label} ); } const styles = StyleSheet.create({ tabBar: { flexDirection: 'row', backgroundColor: '#ffffff', paddingBottom: 20, paddingTop: 12, borderTopWidth: 1, borderTopColor: '#e5e7eb', }, tabButton: { flex: 1, alignItems: 'center', }, tabLabel: { fontSize: 12, marginTop: 4, fontWeight: '500', }, }); // Usage }> {/* screens */} ``` ## Drawer Navigation ```typescript import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerContentComponentProps, } from '@react-navigation/drawer'; const Drawer = createDrawerNavigator(); function CustomDrawerContent(props: DrawerContentComponentProps) { return ( {user.name} {user.email} Log Out ); } function DrawerNavigator() { return ( } screenOptions={{ drawerActiveBackgroundColor: '#ede9fe', drawerActiveTintColor: '#6366f1', drawerInactiveTintColor: '#4b5563', drawerLabelStyle: { marginLeft: -20, fontSize: 15, fontWeight: '500' }, drawerStyle: { width: 280 }, }} > ( ), }} /> ( ), }} /> ); } ``` ## Deep Linking ### Configuration ```typescript // navigation/linking.ts import { LinkingOptions } from '@react-navigation/native'; import { RootStackParamList } from './types'; export const linking: LinkingOptions = { prefixes: ['myapp://', 'https://myapp.com'], config: { screens: { Main: { screens: { Home: 'home', Search: 'search', Profile: 'profile/:userId', }, }, Modal: 'modal/:title', Auth: { screens: { Login: 'login', Register: 'register', ForgotPassword: 'forgot-password', }, }, }, }, // Custom URL parsing getStateFromPath: (path, config) => { // Handle custom URL patterns return getStateFromPath(path, config); }, }; // App.tsx function App() { return ( }> ); } ``` ### Handling Deep Links ```typescript import { useEffect } from "react"; import { Linking } from "react-native"; import { useNavigation } from "@react-navigation/native"; function useDeepLinkHandler() { const navigation = useNavigation(); useEffect(() => { // Handle initial URL const handleInitialUrl = async () => { const url = await Linking.getInitialURL(); if (url) { handleDeepLink(url); } }; // Handle URL changes const subscription = Linking.addEventListener("url", ({ url }) => { handleDeepLink(url); }); handleInitialUrl(); return () => subscription.remove(); }, []); const handleDeepLink = (url: string) => { // Parse URL and navigate const route = parseUrl(url); if (route) { navigation.navigate(route.name, route.params); } }; } ``` ## Navigation State Management ### Auth Flow ```typescript import { createContext, useContext, useState, useEffect } from 'react'; interface AuthContextType { user: User | null; isLoading: boolean; signIn: (email: string, password: string) => Promise; signOut: () => Promise; } const AuthContext = createContext(null!); function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { // Check for existing session checkAuthState(); }, []); const checkAuthState = async () => { try { const token = await AsyncStorage.getItem('token'); if (token) { const user = await fetchUser(token); setUser(user); } } catch (error) { console.error(error); } finally { setIsLoading(false); } }; const signIn = async (email: string, password: string) => { const { user, token } = await loginApi(email, password); await AsyncStorage.setItem('token', token); setUser(user); }; const signOut = async () => { await AsyncStorage.removeItem('token'); setUser(null); }; return ( {children} ); } function RootNavigator() { const { user, isLoading } = useAuth(); if (isLoading) { return ; } return ( {user ? ( ) : ( )} ); } ``` ### Navigation State Persistence ```typescript import AsyncStorage from '@react-native-async-storage/async-storage'; import { NavigationContainer, NavigationState } from '@react-navigation/native'; const PERSISTENCE_KEY = 'NAVIGATION_STATE'; function App() { const [isReady, setIsReady] = useState(false); const [initialState, setInitialState] = useState(); useEffect(() => { const restoreState = async () => { try { const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY); if (savedState) { setInitialState(JSON.parse(savedState)); } } catch (e) { console.error('Failed to restore navigation state:', e); } finally { setIsReady(true); } }; if (!isReady) { restoreState(); } }, [isReady]); if (!isReady) { return ; } return ( { AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)); }} > ); } ``` ## Screen Transitions ### Custom Animations ```typescript import { TransitionPresets } from '@react-navigation/native-stack'; {/* Standard slide transition */} {/* Modal with custom animation */} {/* Full screen modal */} ``` ### Shared Element Transitions ```typescript import { SharedElement } from 'react-navigation-shared-element'; import { createSharedElementStackNavigator } from 'react-navigation-shared-element'; const Stack = createSharedElementStackNavigator(); function ListScreen({ navigation }) { return ( ( navigation.navigate('Detail', { item })}> {item.title} )} /> ); } function DetailScreen({ route }) { const { item } = route.params; return ( {item.title} ); } // Navigator configuration { const { item } = route.params; return [ { id: `item.${item.id}.photo`, animation: 'move' }, { id: `item.${item.id}.title`, animation: 'fade' }, ]; }} /> ``` ## Header Customization ### Custom Header Component ```typescript import { getHeaderTitle } from '@react-navigation/elements'; import { NativeStackHeaderProps } from '@react-navigation/native-stack'; function CustomHeader({ navigation, route, options, back }: NativeStackHeaderProps) { const title = getHeaderTitle(options, route.name); return ( {back && ( )} {title} {options.headerRight && ( {options.headerRight({ canGoBack: !!back })} )} ); } // Usage , }} > {/* screens */} ``` ### Collapsible Header ```typescript import Animated, { useSharedValue, useAnimatedScrollHandler, useAnimatedStyle, interpolate, Extrapolation, } from 'react-native-reanimated'; const HEADER_HEIGHT = 200; const COLLAPSED_HEIGHT = 60; function CollapsibleHeaderScreen() { const scrollY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, }); const headerStyle = useAnimatedStyle(() => { const height = interpolate( scrollY.value, [0, HEADER_HEIGHT - COLLAPSED_HEIGHT], [HEADER_HEIGHT, COLLAPSED_HEIGHT], Extrapolation.CLAMP ); return { height }; }); const titleStyle = useAnimatedStyle(() => { const fontSize = interpolate( scrollY.value, [0, HEADER_HEIGHT - COLLAPSED_HEIGHT], [32, 18], Extrapolation.CLAMP ); return { fontSize }; }); return ( Title {/* Content */} ); } ```