React Native Navigation: Mastering React Navigation v6+

React Native|SEPTEMBER 11, 2025|0 VIEWS
Complete guide to mastering React Navigation v6+ with advanced patterns, performance optimization, and real-world examples

Introduction

Navigation is the backbone of any mobile application, determining how users move between screens and interact with your app. React Navigation v6+ has revolutionized navigation in React Native with improved performance, better TypeScript support, and a more intuitive API.

This comprehensive guide covers everything from basic setup to advanced navigation patterns, helping you build seamless and performant navigation experiences in your React Native applications.

React Navigation v6 introduced significant changes including a new component-based API, improved type safety, and better performance optimizations that make it the go-to solution for React Native navigation.

React Navigation v6 Fundamentals

Installation and Setup

# Install required packages
npm install @react-navigation/native

# Install dependencies for React Native CLI
npm install react-native-screens react-native-safe-area-context

# For iOS, run pod install
cd ios && pod install

# Install navigation types
npm install @react-navigation/stack @react-navigation/bottom-tabs @react-navigation/drawer @react-navigation/native-stack

Basic Navigation Setup

// App.js - Basic navigation setup
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { SafeAreaProvider } from "react-native-safe-area-context";

// Import your screens
import HomeScreen from "./screens/HomeScreen";
import ProfileScreen from "./screens/ProfileScreen";
import SettingsScreen from "./screens/SettingsScreen";

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator
          initialRouteName="Home"
          screenOptions={{
            headerStyle: {
              backgroundColor: "#6200ee",
            },
            headerTintColor: "#fff",
            headerTitleStyle: {
              fontWeight: "bold",
            },
          }}
        >
          <Stack.Screen
            name="Home"
            component={HomeScreen}
            options={{ title: "Welcome Home" }}
          />
          <Stack.Screen
            name="Profile"
            component={ProfileScreen}
            options={{ title: "User Profile" }}
          />
          <Stack.Screen
            name="Settings"
            component={SettingsScreen}
            options={{
              title: "Settings",
              headerRight: () => (
                <TouchableOpacity onPress={() => console.log("Save pressed")}>
                  <Text style={{ color: "#fff", marginRight: 10 }}>Save</Text>
                </TouchableOpacity>
              ),
            }}
          />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

TypeScript Integration

// types/navigation.ts - Define navigation types
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import type { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
import type { CompositeScreenProps } from "@react-navigation/native";

// Define your stack parameter list
export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string; userName?: string };
  Settings: undefined;
  PostDetail: { postId: string; title: string };
  EditProfile: { userId: string };
};

// Define tab navigator parameter list
export type TabParamList = {
  HomeTab: undefined;
  SearchTab: undefined;
  ProfileTab: { userId?: string };
};

// Screen props types
export type HomeScreenProps = NativeStackScreenProps<
  RootStackParamList,
  "Home"
>;
export type ProfileScreenProps = NativeStackScreenProps<
  RootStackParamList,
  "Profile"
>;
export type PostDetailScreenProps = NativeStackScreenProps<
  RootStackParamList,
  "PostDetail"
>;

// Composite navigation props for nested navigators
export type HomeTabScreenProps = CompositeScreenProps<
  BottomTabScreenProps<TabParamList, "HomeTab">,
  NativeStackScreenProps<RootStackParamList>
>;

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}
// screens/HomeScreen.tsx - TypeScript screen implementation
import React from "react";
import { View, Text, Button, StyleSheet } from "react-native";
import type { HomeScreenProps } from "../types/navigation";

export default function HomeScreen({ navigation, route }: HomeScreenProps) {
  const navigateToProfile = () => {
    navigation.navigate("Profile", {
      userId: "12345",
      userName: "John Doe",
    });
  };

  const navigateToPostDetail = () => {
    navigation.navigate("PostDetail", {
      postId: "post-1",
      title: "React Navigation Guide",
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome to React Navigation v6!</Text>

      <Button title="Go to Profile" onPress={navigateToProfile} />

      <Button title="View Post Details" onPress={navigateToPostDetail} />

      <Button
        title="Open Settings"
        onPress={() => navigation.navigate("Settings")}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    marginBottom: 30,
    textAlign: "center",
  },
});

Navigation Patterns and Types

Stack Navigation

// Advanced Stack Navigator configuration
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { Platform } from "react-native";

const Stack = createNativeStackNavigator();

function AppStackNavigator() {
  return (
    <Stack.Navigator
      initialRouteName="Home"
      screenOptions={{
        // Global screen options
        headerStyle: {
          backgroundColor: Platform.OS === "ios" ? "#f8f9fa" : "#6200ee",
        },
        headerTintColor: Platform.OS === "ios" ? "#6200ee" : "#fff",
        headerTitleStyle: {
          fontWeight: "600",
          fontSize: 18,
        },
        // Enable gesture handling
        gestureEnabled: true,
        gestureDirection: "horizontal",
        // Animation configuration
        animation: "slide_from_right",
        // Header configuration
        headerBackTitleVisible: false,
        headerShadowVisible: false,
      }}
    >
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: "Dashboard",
          headerLargeTitle: true, // iOS only
        }}
      />

      <Stack.Screen
        name="Profile"
        component={ProfileScreen}
        options={({ route, navigation }) => ({
          title: route.params?.userName || "Profile",
          headerRight: () => (
            <TouchableOpacity
              onPress={() =>
                navigation.navigate("EditProfile", {
                  userId: route.params.userId,
                })
              }
            >
              <Text style={{ color: "#6200ee" }}>Edit</Text>
            </TouchableOpacity>
          ),
        })}
      />

      <Stack.Screen
        name="PostDetail"
        component={PostDetailScreen}
        options={{
          title: "Post Details",
          presentation: "modal", // Present as modal
          animation: "slide_from_bottom",
        }}
      />

      <Stack.Screen
        name="FullScreenImage"
        component={FullScreenImageScreen}
        options={{
          headerShown: false,
          presentation: "fullScreenModal",
          animation: "fade",
        }}
      />
    </Stack.Navigator>
  );
}

Tab Navigation

// Bottom Tab Navigator with advanced configuration
import React from "react";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { Platform } from "react-native";
import Icon from "react-native-vector-icons/MaterialIcons";

const Tab = createBottomTabNavigator();

function MainTabNavigator() {
  return (
    <Tab.Navigator
      initialRouteName="HomeTab"
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;

          switch (route.name) {
            case "HomeTab":
              iconName = "home";
              break;
            case "SearchTab":
              iconName = "search";
              break;
            case "NotificationsTab":
              iconName = "notifications";
              break;
            case "ProfileTab":
              iconName = "person";
              break;
            default:
              iconName = "help";
          }

          return <Icon name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: "#6200ee",
        tabBarInactiveTintColor: "gray",
        tabBarStyle: {
          backgroundColor: "#fff",
          borderTopWidth: 0,
          elevation: 8,
          shadowOpacity: 0.1,
          shadowRadius: 4,
          shadowColor: "#000",
          shadowOffset: {
            height: -2,
            width: 0,
          },
          paddingBottom: Platform.OS === "ios" ? 20 : 5,
          height: Platform.OS === "ios" ? 80 : 60,
        },
        tabBarLabelStyle: {
          fontSize: 12,
          fontWeight: "500",
        },
        // Header configuration
        headerStyle: {
          backgroundColor: "#6200ee",
        },
        headerTintColor: "#fff",
        headerTitleStyle: {
          fontWeight: "bold",
        },
      })}
    >
      <Tab.Screen
        name="HomeTab"
        component={HomeStackNavigator}
        options={{
          title: "Home",
          tabBarBadge: 3, // Show badge
          headerShown: false, // Hide header for nested stack
        }}
      />

      <Tab.Screen
        name="SearchTab"
        component={SearchScreen}
        options={{
          title: "Search",
          tabBarIcon: ({ focused, color, size }) => (
            <Icon
              name="search"
              size={size}
              color={focused ? "#6200ee" : color}
            />
          ),
        }}
      />

      <Tab.Screen
        name="NotificationsTab"
        component={NotificationsScreen}
        options={{
          title: "Notifications",
          tabBarBadge: 5,
          tabBarBadgeStyle: {
            backgroundColor: "#ff4444",
            color: "#fff",
          },
        }}
      />

      <Tab.Screen
        name="ProfileTab"
        component={ProfileScreen}
        options={{
          title: "Profile",
          tabBarIcon: ({ focused, color, size }) => (
            <View
              style={{
                borderRadius: size / 2,
                borderWidth: focused ? 2 : 0,
                borderColor: "#6200ee",
                padding: focused ? 2 : 0,
              }}
            >
              <Icon name="person" size={size} color={color} />
            </View>
          ),
        }}
      />
    </Tab.Navigator>
  );
}

Drawer Navigation

// Drawer Navigator with custom content
import React from "react";
import {
  createDrawerNavigator,
  DrawerContentScrollView,
  DrawerItem,
} from "@react-navigation/drawer";
import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
import Icon from "react-native-vector-icons/MaterialIcons";

const Drawer = createDrawerNavigator();

// Custom Drawer Content Component
function CustomDrawerContent(props) {
  const { navigation, state } = props;

  return (
    <DrawerContentScrollView {...props} style={styles.drawerContainer}>
      {/* User Profile Section */}
      <View style={styles.profileSection}>
        <Image
          source={{ uri: "https://via.placeholder.com/80" }}
          style={styles.profileImage}
        />
        <Text style={styles.userName}>John Doe</Text>
        <Text style={styles.userEmail}>john.doe@example.com</Text>
      </View>

      {/* Navigation Items */}
      <View style={styles.drawerItems}>
        <DrawerItem
          label="Dashboard"
          onPress={() => navigation.navigate("Dashboard")}
          icon={({ color, size }) => (
            <Icon name="dashboard" color={color} size={size} />
          )}
          focused={state.index === 0}
          activeTintColor="#6200ee"
          inactiveTintColor="#666"
        />

        <DrawerItem
          label="Projects"
          onPress={() => navigation.navigate("Projects")}
          icon={({ color, size }) => (
            <Icon name="folder" color={color} size={size} />
          )}
          focused={state.index === 1}
          activeTintColor="#6200ee"
          inactiveTintColor="#666"
        />

        <DrawerItem
          label="Settings"
          onPress={() => navigation.navigate("Settings")}
          icon={({ color, size }) => (
            <Icon name="settings" color={color} size={size} />
          )}
          focused={state.index === 2}
          activeTintColor="#6200ee"
          inactiveTintColor="#666"
        />

        <DrawerItem
          label="Help & Support"
          onPress={() => navigation.navigate("Help")}
          icon={({ color, size }) => (
            <Icon name="help" color={color} size={size} />
          )}
          activeTintColor="#6200ee"
          inactiveTintColor="#666"
        />
      </View>

      {/* Logout Button */}
      <View style={styles.logoutSection}>
        <TouchableOpacity
          style={styles.logoutButton}
          onPress={() => {
            // Handle logout
            navigation.closeDrawer();
          }}
        >
          <Icon name="logout" size={20} color="#ff4444" />
          <Text style={styles.logoutText}>Logout</Text>
        </TouchableOpacity>
      </View>
    </DrawerContentScrollView>
  );
}

function AppDrawerNavigator() {
  return (
    <Drawer.Navigator
      initialRouteName="Dashboard"
      drawerContent={(props) => <CustomDrawerContent {...props} />}
      screenOptions={{
        drawerStyle: {
          backgroundColor: "#fff",
          width: 280,
        },
        drawerActiveTintColor: "#6200ee",
        drawerInactiveTintColor: "#666",
        drawerLabelStyle: {
          fontSize: 16,
          fontWeight: "500",
        },
        headerStyle: {
          backgroundColor: "#6200ee",
        },
        headerTintColor: "#fff",
        headerTitleStyle: {
          fontWeight: "bold",
        },
      }}
    >
      <Drawer.Screen
        name="Dashboard"
        component={DashboardScreen}
        options={{
          drawerIcon: ({ color, size }) => (
            <Icon name="dashboard" color={color} size={size} />
          ),
        }}
      />

      <Drawer.Screen
        name="Projects"
        component={ProjectsScreen}
        options={{
          drawerIcon: ({ color, size }) => (
            <Icon name="folder" color={color} size={size} />
          ),
        }}
      />

      <Drawer.Screen
        name="Settings"
        component={SettingsScreen}
        options={{
          drawerIcon: ({ color, size }) => (
            <Icon name="settings" color={color} size={size} />
          ),
        }}
      />
    </Drawer.Navigator>
  );
}

const styles = StyleSheet.create({
  drawerContainer: {
    flex: 1,
  },
  profileSection: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: "#e0e0e0",
    alignItems: "center",
  },
  profileImage: {
    width: 80,
    height: 80,
    borderRadius: 40,
    marginBottom: 10,
  },
  userName: {
    fontSize: 18,
    fontWeight: "bold",
    color: "#333",
  },
  userEmail: {
    fontSize: 14,
    color: "#666",
    marginTop: 5,
  },
  drawerItems: {
    flex: 1,
    paddingTop: 10,
  },
  logoutSection: {
    borderTopWidth: 1,
    borderTopColor: "#e0e0e0",
    padding: 20,
  },
  logoutButton: {
    flexDirection: "row",
    alignItems: "center",
  },
  logoutText: {
    marginLeft: 10,
    fontSize: 16,
    color: "#ff4444",
    fontWeight: "500",
  },
});

Advanced Navigation Patterns

Nested Navigators

// Complex nested navigation structure
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { createDrawerNavigator } from "@react-navigation/drawer";

const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
const Drawer = createDrawerNavigator();

// Home Stack Navigator
function HomeStackNavigator() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false, // Hide headers for tab screens
      }}
    >
      <Stack.Screen name="HomeScreen" component={HomeScreen} />
      <Stack.Screen
        name="PostDetail"
        component={PostDetailScreen}
        options={{
          headerShown: true,
          title: "Post Details",
          presentation: "modal",
        }}
      />
      <Stack.Screen
        name="Comments"
        component={CommentsScreen}
        options={{
          headerShown: true,
          title: "Comments",
        }}
      />
    </Stack.Navigator>
  );
}

// Profile Stack Navigator
function ProfileStackNavigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="ProfileScreen" component={ProfileScreen} />
      <Stack.Screen name="EditProfile" component={EditProfileScreen} />
      <Stack.Screen name="ChangePassword" component={ChangePasswordScreen} />
    </Stack.Navigator>
  );
}

// Main Tab Navigator
function MainTabNavigator() {
  return (
    <Tab.Navigator>
      <Tab.Screen
        name="Home"
        component={HomeStackNavigator}
        options={{ headerShown: false }}
      />
      <Tab.Screen name="Search" component={SearchScreen} />
      <Tab.Screen name="Notifications" component={NotificationsScreen} />
      <Tab.Screen
        name="Profile"
        component={ProfileStackNavigator}
        options={{ headerShown: false }}
      />
    </Tab.Navigator>
  );
}

// Root Navigator with Drawer
function RootNavigator() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen
        name="MainTabs"
        component={MainTabNavigator}
        options={{ title: "Dashboard" }}
      />
      <Drawer.Screen name="Settings" component={SettingsScreen} />
      <Drawer.Screen name="Help" component={HelpScreen} />
    </Drawer.Navigator>
  );
}

// App with conditional navigation based on auth state
export default function App() {
  const [isAuthenticated, setIsAuthenticated] = React.useState(false);
  const [isLoading, setIsLoading] = React.useState(true);

  React.useEffect(() => {
    // Check authentication status
    checkAuthStatus().then((authenticated) => {
      setIsAuthenticated(authenticated);
      setIsLoading(false);
    });
  }, []);

  if (isLoading) {
    return <LoadingScreen />;
  }

  return (
    <NavigationContainer>
      {isAuthenticated ? <RootNavigator /> : <AuthStackNavigator />}
    </NavigationContainer>
  );
}
// Modal navigation patterns
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Stack = createNativeStackNavigator();

function AppNavigator() {
  return (
    <Stack.Navigator>
      {/* Main app screens */}
      <Stack.Group>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Group>

      {/* Modal screens */}
      <Stack.Group screenOptions={{ presentation: "modal" }}>
        <Stack.Screen
          name="CreatePost"
          component={CreatePostScreen}
          options={{
            title: "Create Post",
            headerLeft: () => (
              <TouchableOpacity onPress={() => navigation.goBack()}>
                <Text style={{ color: "#6200ee" }}>Cancel</Text>
              </TouchableOpacity>
            ),
            headerRight: () => (
              <TouchableOpacity onPress={handleSave}>
                <Text style={{ color: "#6200ee", fontWeight: "bold" }}>
                  Save
                </Text>
              </TouchableOpacity>
            ),
          }}
        />

        <Stack.Screen
          name="ImagePicker"
          component={ImagePickerScreen}
          options={{
            title: "Select Image",
            presentation: "fullScreenModal",
            animation: "slide_from_bottom",
          }}
        />
      </Stack.Group>

      {/* Overlay screens */}
      <Stack.Group
        screenOptions={{
          presentation: "transparentModal",
          headerShown: false,
          animation: "fade",
        }}
      >
        <Stack.Screen name="Alert" component={CustomAlertScreen} />
        <Stack.Screen name="Loading" component={LoadingOverlayScreen} />
      </Stack.Group>
    </Stack.Navigator>
  );
}

// Modal screen implementation
function CreatePostScreen({ navigation }) {
  const [title, setTitle] = React.useState("");
  const [content, setContent] = React.useState("");

  const handleSave = async () => {
    try {
      await savePost({ title, content });
      navigation.goBack();
    } catch (error) {
      console.error("Failed to save post:", error);
    }
  };

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <TouchableOpacity
          onPress={handleSave}
          disabled={!title.trim() || !content.trim()}
        >
          <Text
            style={{
              color: title.trim() && content.trim() ? "#6200ee" : "#ccc",
              fontWeight: "bold",
            }}
          >
            Save
          </Text>
        </TouchableOpacity>
      ),
    });
  }, [navigation, title, content]);

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.titleInput}
        placeholder="Post title"
        value={title}
        onChangeText={setTitle}
      />
      <TextInput
        style={styles.contentInput}
        placeholder="What's on your mind?"
        multiline
        value={content}
        onChangeText={setContent}
      />
    </View>
  );
}

Deep Linking

// Deep linking configuration
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { Linking } from "react-native";

const linking = {
  prefixes: ["myapp://", "https://myapp.com"],
  config: {
    screens: {
      // Main navigation
      Home: "home",
      Profile: {
        path: "/profile/:userId",
        parse: {
          userId: (userId) => userId,
        },
      },
      PostDetail: {
        path: "/post/:postId",
        parse: {
          postId: (postId) => postId,
        },
      },
      // Nested navigation
      MainTabs: {
        screens: {
          Home: {
            screens: {
              HomeScreen: "feed",
              PostDetail: "feed/post/:postId",
            },
          },
          Search: {
            path: "/search",
            screens: {
              SearchScreen: "",
              SearchResults: "results/:query",
            },
          },
        },
      },
      // Modal screens
      Settings: "settings",
      EditProfile: "profile/edit",
    },
  },
};

export default function App() {
  return (
    <NavigationContainer
      linking={linking}
      fallback={<LoadingScreen />}
      onReady={() => {
        console.log("Navigation container is ready");
      }}
      onStateChange={(state) => {
        console.log("Navigation state changed:", state);
      }}
    >
      <RootNavigator />
    </NavigationContainer>
  );
}

// Handle deep links in components
function PostDetailScreen({ route, navigation }) {
  const { postId } = route.params;
  const [post, setPost] = React.useState(null);

  React.useEffect(() => {
    // Fetch post data based on postId from deep link
    fetchPost(postId)
      .then(setPost)
      .catch((error) => {
        console.error("Failed to fetch post:", error);
        navigation.goBack();
      });
  }, [postId]);

  if (!post) {
    return <LoadingScreen />;
  }

  return (
    <View>
      <Text>{post.title}</Text>
      <Text>{post.content}</Text>
    </View>
  );
}

// Custom hook for handling deep links
function useDeepLinking() {
  React.useEffect(() => {
    const handleDeepLink = (url) => {
      console.log("Deep link received:", url);
      // Handle custom deep link logic
    };

    // Handle app launch from deep link
    Linking.getInitialURL().then((url) => {
      if (url) {
        handleDeepLink(url);
      }
    });

    // Handle deep links when app is running
    const subscription = Linking.addEventListener("url", ({ url }) => {
      handleDeepLink(url);
    });

    return () => subscription?.remove();
  }, []);
}

Navigation State Management

// Navigation state persistence
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import AsyncStorage from "@react-native-async-storage/async-storage";

const PERSISTENCE_KEY = "NAVIGATION_STATE_V1";

export default function App() {
  const [isReady, setIsReady] = React.useState(false);
  const [initialState, setInitialState] = React.useState();

  React.useEffect(() => {
    const restoreState = async () => {
      try {
        const initialUrl = await Linking.getInitialURL();

        if (Platform.OS !== "web" && initialUrl == null) {
          // Only restore state if there's no deep link and we're not on web
          const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
          const state = savedStateString
            ? JSON.parse(savedStateString)
            : undefined;

          if (state !== undefined) {
            setInitialState(state);
          }
        }
      } finally {
        setIsReady(true);
      }
    };

    if (!isReady) {
      restoreState();
    }
  }, [isReady]);

  const handleStateChange = (state) => {
    AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
  };

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      initialState={initialState}
      onStateChange={handleStateChange}
    >
      <RootNavigator />
    </NavigationContainer>
  );
}
// Navigation events and listeners
import React from "react";
import { useFocusEffect, useNavigation } from "@react-navigation/native";

function HomeScreen() {
  const navigation = useNavigation();

  // Focus effect - runs when screen comes into focus
  useFocusEffect(
    React.useCallback(() => {
      console.log("Screen focused");

      // Fetch fresh data
      fetchHomeData();

      // Cleanup function
      return () => {
        console.log("Screen unfocused");
      };
    }, [])
  );

  // Navigation listeners
  React.useEffect(() => {
    const unsubscribeBeforeRemove = navigation.addListener(
      "beforeRemove",
      (e) => {
        // Prevent default behavior of leaving the screen
        e.preventDefault();

        // Show confirmation dialog
        Alert.alert(
          "Discard changes?",
          "You have unsaved changes. Are you sure to discard them?",
          [
            { text: "Don't leave", style: "cancel", onPress: () => {} },
            {
              text: "Discard",
              style: "destructive",
              onPress: () => navigation.dispatch(e.data.action),
            },
          ]
        );
      }
    );

    const unsubscribeTabPress = navigation.addListener("tabPress", (e) => {
      // Prevent default behavior
      e.preventDefault();

      // Custom tab press handling
      console.log("Tab pressed");
    });

    return () => {
      unsubscribeBeforeRemove();
      unsubscribeTabPress();
    };
  }, [navigation]);

  return <View>{/* Screen content */}</View>;
}

// Global navigation event handling
function useNavigationEvents() {
  const navigation = useNavigation();

  React.useEffect(() => {
    const unsubscribe = navigation.addListener("state", (e) => {
      // Track navigation for analytics
      const currentRoute = navigation.getCurrentRoute();
      analytics.track("screen_view", {
        screen_name: currentRoute?.name,
        screen_params: currentRoute?.params,
      });
    });

    return unsubscribe;
  }, [navigation]);
}

Performance Optimization

Lazy Loading and Code Splitting

// Lazy loading screens for better performance
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Stack = createNativeStackNavigator();

// Lazy load screens
const HomeScreen = React.lazy(() => import("./screens/HomeScreen"));
const ProfileScreen = React.lazy(() => import("./screens/ProfileScreen"));
const SettingsScreen = React.lazy(() => import("./screens/SettingsScreen"));

// Loading component
function LoadingScreen() {
  return (
    <View style={styles.loadingContainer}>
      <ActivityIndicator size="large" color="#6200ee" />
      <Text>Loading...</Text>
    </View>
  );
}

function AppNavigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={React.memo(() => (
          <React.Suspense fallback={<LoadingScreen />}>
            <HomeScreen />
          </React.Suspense>
        ))}
      />

      <Stack.Screen
        name="Profile"
        component={React.memo(() => (
          <React.Suspense fallback={<LoadingScreen />}>
            <ProfileScreen />
          </React.Suspense>
        ))}
      />

      <Stack.Screen
        name="Settings"
        component={React.memo(() => (
          <React.Suspense fallback={<LoadingScreen />}>
            <SettingsScreen />
          </React.Suspense>
        ))}
      />
    </Stack.Navigator>
  );
}

// Optimized screen component with memo
const OptimizedHomeScreen = React.memo(function HomeScreen({
  navigation,
  route,
}) {
  const [data, setData] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  const fetchData = React.useCallback(async () => {
    try {
      setLoading(true);
      const result = await api.fetchHomeData();
      setData(result);
    } catch (error) {
      console.error("Failed to fetch data:", error);
    } finally {
      setLoading(false);
    }
  }, []);

  useFocusEffect(
    React.useCallback(() => {
      fetchData();
    }, [fetchData])
  );

  if (loading) {
    return <LoadingScreen />;
  }

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ListItem item={item} />}
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      windowSize={10}
      initialNumToRender={5}
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
    />
  );
});
// Navigation performance optimizations
import React from "react";
import { InteractionManager } from "react-native";

// Optimize screen transitions
function OptimizedScreen({ navigation }) {
  const [isReady, setIsReady] = React.useState(false);

  React.useEffect(() => {
    const task = InteractionManager.runAfterInteractions(() => {
      setIsReady(true);
    });

    return () => task.cancel();
  }, []);

  if (!isReady) {
    return <LoadingScreen />;
  }

  return <View>{/* Screen content */}</View>;
}

// Preload screens for faster navigation
function usePreloadScreens() {
  React.useEffect(() => {
    const preloadScreens = async () => {
      // Preload critical screens
      await Promise.all([
        import("./screens/ProfileScreen"),
        import("./screens/SearchScreen"),
      ]);
    };

    const timer = setTimeout(preloadScreens, 1000);
    return () => clearTimeout(timer);
  }, []);
}

// Optimize re-renders with navigation context
const NavigationContext = React.createContext();

function NavigationProvider({ children }) {
  const [currentRoute, setCurrentRoute] = React.useState("Home");

  const navigationState = React.useMemo(
    () => ({
      currentRoute,
      setCurrentRoute,
    }),
    [currentRoute]
  );

  return (
    <NavigationContext.Provider value={navigationState}>
      {children}
    </NavigationContext.Provider>
  );
}

// Custom hook for navigation state
function useNavigationState() {
  const context = React.useContext(NavigationContext);
  if (!context) {
    throw new Error(
      "useNavigationState must be used within NavigationProvider"
    );
  }
  return context;
}

Custom Navigation Components

Custom Tab Bar

// Custom tab bar implementation
import React from "react";
import {
  View,
  TouchableOpacity,
  Text,
  Animated,
  Dimensions,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialIcons";

const { width } = Dimensions.get("window");

function CustomTabBar({ state, descriptors, navigation }) {
  const insets = useSafeAreaInsets();
  const tabWidth = width / state.routes.length;

  // Animated indicator
  const translateX = React.useRef(new Animated.Value(0)).current;

  React.useEffect(() => {
    Animated.spring(translateX, {
      toValue: state.index * tabWidth,
      useNativeDriver: true,
      tension: 100,
      friction: 8,
    }).start();
  }, [state.index, tabWidth]);

  return (
    <View style={[styles.tabBarContainer, { paddingBottom: insets.bottom }]}>
      {/* Animated indicator */}
      <Animated.View
        style={[
          styles.tabIndicator,
          {
            width: tabWidth,
            transform: [{ translateX }],
          },
        ]}
      />

      {/* Tab buttons */}
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        const label = options.tabBarLabel || options.title || 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);
          }
        };

        const onLongPress = () => {
          navigation.emit({
            type: "tabLongPress",
            target: route.key,
          });
        };

        // Icon mapping
        const getIconName = (routeName) => {
          switch (routeName) {
            case "Home":
              return "home";
            case "Search":
              return "search";
            case "Notifications":
              return "notifications";
            case "Profile":
              return "person";
            default:
              return "help";
          }
        };

        return (
          <TouchableOpacity
            key={route.key}
            accessibilityRole="button"
            accessibilityState={isFocused ? { selected: true } : {}}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            testID={options.tabBarTestID}
            onPress={onPress}
            onLongPress={onLongPress}
            style={styles.tabButton}
          >
            <View style={styles.tabContent}>
              <Icon
                name={getIconName(route.name)}
                size={24}
                color={isFocused ? "#6200ee" : "#666"}
              />
              <Text
                style={[
                  styles.tabLabel,
                  { color: isFocused ? "#6200ee" : "#666" },
                ]}
              >
                {label}
              </Text>
            </View>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  tabBarContainer: {
    flexDirection: "row",
    backgroundColor: "#fff",
    borderTopWidth: 1,
    borderTopColor: "#e0e0e0",
    paddingTop: 8,
    elevation: 8,
    shadowOpacity: 0.1,
    shadowRadius: 4,
    shadowColor: "#000",
    shadowOffset: {
      height: -2,
      width: 0,
    },
  },
  tabIndicator: {
    position: "absolute",
    top: 0,
    height: 3,
    backgroundColor: "#6200ee",
  },
  tabButton: {
    flex: 1,
    alignItems: "center",
    paddingVertical: 8,
  },
  tabContent: {
    alignItems: "center",
  },
  tabLabel: {
    fontSize: 12,
    fontWeight: "500",
    marginTop: 4,
  },
});

Custom Header

// Custom header implementation
import React from "react";
import { View, Text, TouchableOpacity, Animated } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialIcons";

function CustomHeader({
  title,
  subtitle,
  leftButton,
  rightButton,
  backgroundColor = "#6200ee",
  textColor = "#fff",
  onTitlePress,
  showSubtitle = false,
}) {
  const insets = useSafeAreaInsets();
  const scrollY = React.useRef(new Animated.Value(0)).current;

  // Animated header opacity based on scroll
  const headerOpacity = scrollY.interpolate({
    inputRange: [0, 100],
    outputRange: [0.9, 1],
    extrapolate: "clamp",
  });

  const titleScale = scrollY.interpolate({
    inputRange: [0, 50],
    outputRange: [1.2, 1],
    extrapolate: "clamp",
  });

  return (
    <Animated.View
      style={[
        styles.headerContainer,
        {
          backgroundColor,
          paddingTop: insets.top,
          opacity: headerOpacity,
        },
      ]}
    >
      <View style={styles.headerContent}>
        {/* Left button */}
        <View style={styles.headerSide}>
          {leftButton && (
            <TouchableOpacity
              style={styles.headerButton}
              onPress={leftButton.onPress}
            >
              <Icon name={leftButton.icon} size={24} color={textColor} />
            </TouchableOpacity>
          )}
        </View>

        {/* Title section */}
        <TouchableOpacity
          style={styles.titleContainer}
          onPress={onTitlePress}
          disabled={!onTitlePress}
        >
          <Animated.Text
            style={[
              styles.headerTitle,
              {
                color: textColor,
                transform: [{ scale: titleScale }],
              },
            ]}
            numberOfLines={1}
          >
            {title}
          </Animated.Text>
          {showSubtitle && subtitle && (
            <Text style={[styles.headerSubtitle, { color: textColor }]}>
              {subtitle}
            </Text>
          )}
        </TouchableOpacity>

        {/* Right button */}
        <View style={styles.headerSide}>
          {rightButton && (
            <TouchableOpacity
              style={styles.headerButton}
              onPress={rightButton.onPress}
            >
              {rightButton.icon ? (
                <Icon name={rightButton.icon} size={24} color={textColor} />
              ) : (
                <Text style={[styles.headerButtonText, { color: textColor }]}>
                  {rightButton.text}
                </Text>
              )}
            </TouchableOpacity>
          )}
        </View>
      </View>
    </Animated.View>
  );
}

// Usage in screen
function ScreenWithCustomHeader({ navigation }) {
  return (
    <View style={styles.container}>
      <CustomHeader
        title="Custom Header"
        subtitle="Subtitle here"
        showSubtitle={true}
        leftButton={{
          icon: "arrow-back",
          onPress: () => navigation.goBack(),
        }}
        rightButton={{
          icon: "more-vert",
          onPress: () => console.log("More pressed"),
        }}
        onTitlePress={() => console.log("Title pressed")}
      />

      <View style={styles.content}>{/* Screen content */}</View>
    </View>
  );
}

const styles = StyleSheet.create({
  headerContainer: {
    elevation: 4,
    shadowOpacity: 0.2,
    shadowRadius: 4,
    shadowColor: "#000",
    shadowOffset: {
      height: 2,
      width: 0,
    },
  },
  headerContent: {
    flexDirection: "row",
    alignItems: "center",
    paddingHorizontal: 16,
    paddingVertical: 12,
    minHeight: 56,
  },
  headerSide: {
    width: 40,
    alignItems: "center",
  },
  headerButton: {
    padding: 8,
  },
  headerButtonText: {
    fontSize: 16,
    fontWeight: "500",
  },
  titleContainer: {
    flex: 1,
    alignItems: "center",
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: "bold",
  },
  headerSubtitle: {
    fontSize: 14,
    opacity: 0.8,
    marginTop: 2,
  },
  container: {
    flex: 1,
  },
  content: {
    flex: 1,
  },
});

Testing Navigation

Unit Testing Navigation

// Testing navigation with Jest and React Native Testing Library
import React from "react";
import { render, fireEvent, waitFor } from "@testing-library/react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

import HomeScreen from "../screens/HomeScreen";
import ProfileScreen from "../screens/ProfileScreen";

const Stack = createNativeStackNavigator();

function TestNavigator({ initialRouteName = "Home" }) {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName={initialRouteName}>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

describe("Navigation Tests", () => {
  it("should navigate to profile screen when button is pressed", async () => {
    const { getByText } = render(<TestNavigator />);

    const navigateButton = getByText("Go to Profile");
    fireEvent.press(navigateButton);

    await waitFor(() => {
      expect(getByText("Profile Screen")).toBeTruthy();
    });
  });

  it("should pass correct parameters when navigating", async () => {
    const { getByText } = render(<TestNavigator />);

    const navigateButton = getByText("View User Profile");
    fireEvent.press(navigateButton);

    await waitFor(() => {
      expect(getByText("User: John Doe")).toBeTruthy();
    });
  });

  it("should go back to previous screen", async () => {
    const { getByText } = render(<TestNavigator initialRouteName="Profile" />);

    const backButton = getByText("Back");
    fireEvent.press(backButton);

    await waitFor(() => {
      expect(getByText("Home Screen")).toBeTruthy();
    });
  });
});

// Mock navigation for component testing
const createMockNavigation = (overrides = {}) => ({
  navigate: jest.fn(),
  goBack: jest.fn(),
  replace: jest.fn(),
  reset: jest.fn(),
  setOptions: jest.fn(),
  addListener: jest.fn(() => jest.fn()),
  removeListener: jest.fn(),
  ...overrides,
});

// Test component with mocked navigation
describe("HomeScreen Component", () => {
  it("should call navigate with correct parameters", () => {
    const mockNavigation = createMockNavigation();
    const mockRoute = { params: {} };

    const { getByText } = render(
      <HomeScreen navigation={mockNavigation} route={mockRoute} />
    );

    const profileButton = getByText("Go to Profile");
    fireEvent.press(profileButton);

    expect(mockNavigation.navigate).toHaveBeenCalledWith("Profile", {
      userId: "12345",
      userName: "John Doe",
    });
  });
});

Integration Testing

// E2E testing with Detox
describe("Navigation Flow", () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  beforeEach(async () => {
    await device.reloadReactNative();
  });

  it("should navigate through main flow", async () => {
    // Test tab navigation
    await element(by.text("Home")).tap();
    await expect(element(by.text("Welcome Home"))).toBeVisible();

    await element(by.text("Search")).tap();
    await expect(element(by.text("Search Screen"))).toBeVisible();

    // Test stack navigation
    await element(by.text("View Profile")).tap();
    await expect(element(by.text("User Profile"))).toBeVisible();

    // Test going back
    await element(by.text("Back")).tap();
    await expect(element(by.text("Search Screen"))).toBeVisible();
  });

  it("should handle deep links correctly", async () => {
    await device.openURL({
      url: "myapp://profile/12345",
    });

    await expect(element(by.text("Profile Details"))).toBeVisible();
    await expect(element(by.text("User ID: 12345"))).toBeVisible();
  });

  it("should persist navigation state", async () => {
    // Navigate to a specific screen
    await element(by.text("Settings")).tap();
    await expect(element(by.text("Settings Screen"))).toBeVisible();

    // Background and relaunch app
    await device.sendToHome();
    await device.launchApp({ newInstance: false });

    // Should be on the same screen
    await expect(element(by.text("Settings Screen"))).toBeVisible();
  });
});

Best Practices and Common Patterns

// Navigation best practices implementation
const navigationBestPractices = {
  // 1. Use proper TypeScript types
  typeSafety: `
    // Define param lists for type safety
    type RootStackParamList = {
      Home: undefined;
      Profile: { userId: string };
      Settings: undefined;
    };

    // Use typed navigation hooks
    const navigation = useNavigation<NavigationProp<RootStackParamList>>();
    const route = useRoute<RouteProp<RootStackParamList, 'Profile'>>();
  `,

  // 2. Optimize performance
  performance: {
    lazyLoading: "Use React.lazy() for non-critical screens",
    memoization: "Memo screens to prevent unnecessary re-renders",
    preloading: "Preload critical screens after app launch",
    codeExample: `
      const LazyScreen = React.lazy(() => import('./LazyScreen'));
      const MemoizedScreen = React.memo(MyScreen);
      
      // Use InteractionManager for heavy operations
      React.useEffect(() => {
        InteractionManager.runAfterInteractions(() => {
          // Heavy operations here
        });
      }, []);
    `,
  },

  // 3. Handle navigation state properly
  stateManagement: {
    persistence: "Implement navigation state persistence",
    resetOnLogout: "Reset navigation stack on logout",
    deepLinking: "Handle deep links gracefully",
    codeExample: `
      // Reset navigation stack on logout
      const logout = () => {
        navigation.reset({
          index: 0,
          routes: [{ name: 'Login' }],
        });
      };

      // Handle deep links with error boundaries
      const handleDeepLink = (url) => {
        try {
          const route = parseDeepLink(url);
          navigation.navigate(route.name, route.params);
        } catch (error) {
          console.error('Invalid deep link:', error);
          navigation.navigate('Home');
        }
      };
    `,
  },

  // 4. Accessibility
  accessibility: {
    labels: "Provide proper accessibility labels",
    announcements: "Use screen reader announcements",
    navigation: "Support keyboard navigation",
    codeExample: `
      <TouchableOpacity
        accessibilityRole="button"
        accessibilityLabel="Navigate to profile"
        accessibilityHint="Opens your user profile screen"
        onPress={() => navigation.navigate('Profile')}
      >
        <Text>Profile</Text>
      </TouchableOpacity>
    `,
  },
};

// Error boundaries for navigation
class NavigationErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Navigation error:", error, errorInfo);
    // Log to crash reporting service
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={styles.errorContainer}>
          <Text style={styles.errorText}>Something went wrong</Text>
          <Button
            title="Go to Home"
            onPress={() => {
              this.setState({ hasError: false });
              this.props.navigation.navigate("Home");
            }}
          />
        </View>
      );
    }

    return this.props.children;
  }
}

Common Navigation Patterns

// Common navigation patterns and solutions
const commonPatterns = {
  // 1. Conditional navigation based on auth state
  conditionalNavigation: `
    function RootNavigator() {
      const { user, loading } = useAuth();

      if (loading) {
        return <LoadingScreen />;
      }

      return user ? <AuthenticatedNavigator /> : <AuthNavigator />;
    }
  `,

  // 2. Tab navigator with nested stacks
  nestedTabStacks: `
    function HomeStackNavigator() {
      return (
        <Stack.Navigator>
          <Stack.Screen name="HomeScreen" component={HomeScreen} />
          <Stack.Screen name="Details" component={DetailsScreen} />
        </Stack.Navigator>
      );
    }

    function MainTabNavigator() {
      return (
        <Tab.Navigator>
          <Tab.Screen 
            name="Home" 
            component={HomeStackNavigator}
            options={{ headerShown: false }}
          />
        </Tab.Navigator>
      );
    }
  `,

  // 3. Modal navigation pattern
  modalPattern: `
    function ModalNavigator() {
      return (
        <Stack.Navigator>
          <Stack.Group>
            <Stack.Screen name="Main" component={MainNavigator} />
          </Stack.Group>
          <Stack.Group screenOptions={{ presentation: 'modal' }}>
            <Stack.Screen name="Settings" component={SettingsScreen} />
            <Stack.Screen name="CreatePost" component={CreatePostScreen} />
          </Stack.Group>
        </Stack.Navigator>
      );
    }
  `,

  // 4. Bottom sheet navigation
  bottomSheetPattern: `
    function BottomSheetNavigator() {
      const [isVisible, setIsVisible] = React.useState(false);

      return (
        <View style={styles.container}>
          <MainContent />
          <BottomSheet
            isVisible={isVisible}
            onClose={() => setIsVisible(false)}
          >
            <NavigationContainer independent={true}>
              <Stack.Navigator>
                <Stack.Screen name="Option1" component={Option1Screen} />
                <Stack.Screen name="Option2" component={Option2Screen} />
              </Stack.Navigator>
            </NavigationContainer>
          </BottomSheet>
        </View>
      );
    }
  `,
};

Conclusion

React Navigation v6+ has transformed mobile app development in React Native by providing a powerful, flexible, and performant navigation solution. This guide covered the essential concepts and advanced patterns needed to build professional mobile applications.

Key Takeaways

  1. Modern API: React Navigation v6 offers a component-based API that's more intuitive and type-safe
  2. Performance: Proper implementation with lazy loading and optimization techniques ensures smooth navigation
  3. Flexibility: Support for complex navigation patterns including nested navigators, modals, and deep linking
  4. TypeScript: Full TypeScript support improves development experience and reduces runtime errors
  5. Customization: Extensive customization options for headers, tabs, and navigation behavior

Best Practices Summary

  • Always use TypeScript for better type safety and development experience
  • Implement proper navigation state persistence for better user experience
  • Optimize performance with lazy loading and memoization
  • Handle deep links gracefully with error boundaries
  • Test navigation flows thoroughly with both unit and integration tests
  • Follow accessibility guidelines for inclusive app design

Next Steps

  • Implement navigation state management with Redux or Context
  • Add navigation analytics and user behavior tracking
  • Explore advanced animation patterns with Reanimated
  • Build custom navigation components for unique app experiences
  • Implement offline navigation state handling

React Navigation v6+ provides all the tools needed to create exceptional navigation experiences in React Native applications. Master these patterns and techniques to build apps that users love to navigate through.