MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

React Native Navigation: React Navigation v5

Implement navigation in React Native: stack, tab, and drawer navigators.

React NativeNavigationReact NavigationMobile

By MinhVo

Introduction

Navigation is the backbone of any mobile application. Users expect smooth, intuitive transitions between screens — swipe gestures to go back, bottom tabs for primary sections, and drawer menus for secondary navigation. React Navigation v5 delivers all of this through a completely rewritten, hook-based API that replaces the static configuration of v4 with a composable, component-driven architecture.

React Navigation v5 is the most widely adopted navigation library in the React Native ecosystem, powering navigation in millions of applications. Its component-based API feels natural to React developers, its TypeScript support is excellent, and its pluggable architecture allows deep customization of every aspect of the navigation experience. Whether you're building a simple two-screen app or a complex application with nested navigators, authentication flows, and deep linking, React Navigation v5 provides the building blocks you need.

Mobile Navigation Patterns

Understanding React Navigation v5: Core Concepts

The Navigator Component Model

The fundamental shift in v5 is that navigators are React components, not configuration objects. Each navigator type (Stack, Tab, Drawer) is a component that accepts Screen children:

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
 
const Stack = createStackNavigator();
 
function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

The Navigation Container

NavigationContainer is the root component that manages the navigation tree and handles deep linking. Every navigator in your app must be nested inside a single NavigationContainer:

import { NavigationContainer, DefaultTheme, DarkTheme } from '@react-navigation/native';
import { useColorScheme } from 'react-native';
 
export default function App() {
  const scheme = useColorScheme();
 
  return (
    <NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
      <RootNavigator />
    </NavigationContainer>
  );
}

Hooks-Based API

v5 introduces hooks for all navigation operations, replacing the this.props.navigation pattern:

import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
 
function ProductScreen() {
  const navigation = useNavigation();
  const route = useRoute();
  const { productId } = route.params;
 
  useFocusEffect(
    useCallback(() => {
      // Screen is focused — load fresh data
      fetchProduct(productId);
      return () => {
        // Screen loses focus — cleanup
        cancelPendingRequests();
      };
    }, [productId])
  );
 
  return (
    <View>
      <Text>Product #{productId}</Text>
      <Button
        title="View Reviews"
        onPress={() => navigation.navigate('Reviews', { productId })}
      />
    </View>
  );
}

App Navigation Structure

Architecture and Navigator Types

Stack Navigator

The stack navigator manages a stack of screens where each new screen is pushed on top and the back button pops it off. This is the most common navigation pattern for detail flows:

import { createStackNavigator } from '@react-navigation/stack';
 
type RootStackParamList = {
  Home: undefined;
  ProductDetail: { id: string; title: string };
  Checkout: { cartItems: CartItem[] };
};
 
const Stack = createStackNavigator<RootStackParamList>();
 
function RootNavigator() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle: { backgroundColor: '#3b82f6' },
        headerTintColor: '#fff',
        headerTitleStyle: { fontWeight: 'bold' },
        gestureEnabled: true,
        cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      }}
    >
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'Products' }}
      />
      <Stack.Screen
        name="ProductDetail"
        component={ProductDetailScreen}
        options={({ route }) => ({
          title: route.params.title,
          headerRight: () => (
            <ShareButton productId={route.params.id} />
          ),
        })}
      />
      <Stack.Screen
        name="Checkout"
        component={CheckoutScreen}
        options={{
          gestureEnabled: false, // Prevent swipe back during checkout
          headerLeft: () => null,
        }}
      />
    </Stack.Navigator>
  );
}

Bottom Tab Navigator

Tabs provide primary navigation between top-level sections of your app:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
 
const Tab = createBottomTabNavigator();
 
function MainTabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName: keyof typeof Ionicons.glyphMap;
 
          switch (route.name) {
            case 'Home':
              iconName = focused ? 'home' : 'home-outline';
              break;
            case 'Search':
              iconName = focused ? 'search' : 'search-outline';
              break;
            case 'Cart':
              iconName = focused ? 'cart' : 'cart-outline';
              break;
            case 'Profile':
              iconName = focused ? 'person' : 'person-outline';
              break;
          }
 
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#3b82f6',
        tabBarInactiveTintColor: '#6b7280',
        tabBarStyle: {
          paddingBottom: 8,
          paddingTop: 8,
          height: 60,
        },
      })}
    >
      <Tab.Screen name="Home" component={HomeStack} />
      <Tab.Screen name="Search" component={SearchStack} />
      <Tab.Screen name="Cart" component={CartStack} options={{ tabBarBadge: 3 }} />
      <Tab.Screen name="Profile" component={ProfileStack} />
    </Tab.Navigator>
  );
}

Drawer Navigator

The drawer provides a side menu for secondary navigation:

import { createDrawerNavigator, DrawerContentScrollView, DrawerItem } from '@react-navigation/drawer';
 
const Drawer = createDrawerNavigator();
 
function CustomDrawerContent(props: any) {
  const { signOut } = useAuth();
 
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItem
        label="Home"
        icon={({ color, size }) => (
          <Ionicons name="home-outline" size={size} color={color} />
        )}
        onPress={() => props.navigation.navigate('Main')}
      />
      <DrawerItem
        label="Settings"
        icon={({ color, size }) => (
          <Ionicons name="settings-outline" size={size} color={color} />
        )}
        onPress={() => props.navigation.navigate('Settings')}
      />
      <DrawerItem
        label="Sign Out"
        icon={({ color, size }) => (
          <Ionicons name="log-out-outline" size={size} color={color} />
        )}
        onPress={signOut}
      />
    </DrawerContentScrollView>
  );
}
 
function AppDrawer() {
  return (
    <Drawer.Navigator drawerContent={(props) => <CustomDrawerContent {...props} />}>
      <Drawer.Screen name="Main" component={MainTabNavigator} />
      <Drawer.Screen name="Settings" component={SettingsScreen} />
    </Drawer.Navigator>
  );
}

Step-by-Step Implementation

Installation

# Core package
npm install @react-navigation/native
 
# Dependencies
npm install react-native-screens react-native-safe-area-context
 
# Navigator types (install what you need)
npm install @react-navigation/stack @react-navigation/bottom-tabs @react-navigation/drawer
 
# For gesture support
npm install react-native-gesture-handler
 
# For tab bar icons
npm install @expo/vector-icons

Setting Up the Root Structure

// App.tsx
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import { AuthNavigator } from './navigators/AuthNavigator';
import { AppNavigator } from './navigators/AppNavigator';
import { LoadingScreen } from './screens/LoadingScreen';
 
function RootNavigation() {
  const { isAuthenticated, isLoading } = useAuth();
 
  if (isLoading) return <LoadingScreen />;
 
  return (
    <NavigationContainer>
      {isAuthenticated ? <AppNavigator /> : <AuthNavigator />}
    </NavigationContainer>
  );
}
 
export default function App() {
  return (
    <AuthProvider>
      <RootNavigation />
    </AuthProvider>
  );
}

Type-Safe Navigation with TypeScript

Define your navigation types for compile-time safety:

// types/navigation.ts
import { NavigatorScreenParams } from '@react-navigation/native';
 
export type RootStackParamList = {
  Auth: NavigatorScreenParams<AuthStackParamList>;
  Main: NavigatorScreenParams<MainTabParamList>;
  Modal: { title: string; content: string };
};
 
export type AuthStackParamList = {
  Login: undefined;
  Register: undefined;
  ForgotPassword: { email?: string };
};
 
export type MainTabParamList = {
  HomeStack: NavigatorScreenParams<HomeStackParamList>;
  Search: { initialQuery?: string };
  Profile: { userId?: string };
};
 
export type HomeStackParamList = {
  ProductList: { category?: string };
  ProductDetail: { id: string };
};

Navigation Flow Diagram

Real-World Use Cases

Use Case 1: Authentication Flow with Conditional Navigation

function AppNavigator() {
  const { isAuthenticated } = useAuth();
 
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      {isAuthenticated ? (
        <>
          <Stack.Screen name="Main" component={MainTabNavigator} />
          <Stack.Screen
            name="Modal"
            component={ModalScreen}
            options={{ presentation: 'modal' }}
          />
        </>
      ) : (
        <>
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="Register" component={RegisterScreen} />
        </>
      )}
    </Stack.Navigator>
  );
}

Use Case 2: Deep Linking Configuration

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Main: {
        screens: {
          HomeStack: {
            screens: {
              ProductList: 'products',
              ProductDetail: 'products/:id',
            },
          },
          Search: 'search',
          Profile: 'profile/:userId?',
        },
      },
      Modal: 'modal/:title',
    },
  },
};
 
function App() {
  return (
    <NavigationContainer linking={linking}>
      <AppNavigator />
    </NavigationContainer>
  );
}

Use Case 3: Nested Stacks Within Tabs

Each tab typically maintains its own navigation stack so users can drill into detail screens without losing tab state:

const HomeStack = createStackNavigator<HomeStackParamList>();
 
function HomeStackNavigator() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen name="ProductList" component={ProductListScreen} />
      <HomeStack.Screen name="ProductDetail" component={ProductDetailScreen} />
    </HomeStack.Navigator>
  );
}
 
// Then use HomeStackNavigator as a tab screen
<Tab.Screen name="Home" component={HomeStackNavigator} />

Best Practices for Production

  1. Define navigation types upfront: Create a comprehensive TypeScript type definition for all navigation params before building screens. This catches navigation errors at compile time.

  2. Use navigation.reset() for auth state changes: When logging out, reset the navigation state instead of navigating back, which could expose authenticated screens during the transition.

  3. Implement proper back behavior on Android: Use BackHandler API for custom back button handling, but ensure the default stack pop behavior works correctly in all nested navigator scenarios.

  4. Optimize header rendering: Use options as a function of route params to compute header elements dynamically. Memoize expensive header components to prevent re-renders during transitions.

  5. Handle deep links gracefully: Test deep links with npx uri-scheme open myapp://products/123 --android and verify that parameters are correctly parsed and passed to screens.

  6. Use unmountOnBlur selectively: By default, screens stay mounted when navigating away. Set unmountOnBlur: true for screens that need fresh data on each visit (like search results).

  7. Implement proper loading states: Use useFocusEffect to refetch data when a screen regains focus, showing loading indicators only when the data actually needs refreshing.

  8. Test navigation flows in isolation: Use @react-navigation/test-renderer to test navigation transitions and screen configuration without running a full app.

Common Pitfalls and Solutions

PitfallImpactSolution
Multiple NavigationContainer instancesNavigation state conflicts and broken deep linksEnsure only one NavigationContainer wraps your entire app
Passing non-serializable params (functions, class instances)State persistence and deep linking break silentlyUse route params only for serializable data; pass callbacks via context
Tab navigator inside stack loses state on backUser's scroll position and input state lostKeep tab navigator as the root; nest stacks inside each tab
Gesture handler not initializedSwipe-back gesture crashes the appImport react-native-gesture-handler at the top of your entry file
Deep link params not reaching nested screenDeep link navigates to wrong screenVerify the linking config matches your navigator hierarchy exactly
Header button re-rendering every framePerformance jank during transitionsMemoize header components with React.memo and stable callbacks

Performance Optimization

Preventing Unnecessary Re-renders

// Memoize screen components
const ProductDetailScreen = React.memo(function ProductDetail() {
  const route = useRoute<RouteProp<RootStackParamList, 'ProductDetail'>>();
  // Component only re-renders when route params change
  return <ProductDetailContent id={route.params.id} />;
});
 
// Use stable callbacks for navigation
function useStableNavigate() {
  const navigation = useNavigation();
  return useCallback(
    (screen: string, params?: object) => navigation.navigate(screen, params),
    [navigation]
  );
}

Lazy Loading Screens

<Tab.Navigator
  lazy={true}  // Only render tab screens when they're first visited
  tabBarOptions={{
    lazy: true,
  }}
>
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="Search" component={SearchScreen} />
  <Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>

Comparison with Alternatives

FeatureReact Navigation v5React Native Navigation (Wix)Expo Router
ArchitectureJS-based navigationTruly native navigation controllersFile-system routing on React Navigation
PerformanceGood (bridge-based)Excellent (native)Good (JS-based)
CustomizationHighly customizableLimited to native capabilitiesFile-system conventions
Deep LinkingManual configurationManual configurationAutomatic
TypeScriptStrong type supportGood type supportBuilt-in typed routes
CommunityLargest ecosystemSmaller communityGrowing rapidly
Learning CurveMediumHighLow
Web SupportVia React Native WebNot supportedFirst-class

Testing Strategies

import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
 
function renderWithNavigation(component: React.ReactElement) {
  const Stack = createStackNavigator();
  return render(
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Test" component={() => component} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
 
describe('Product Navigation', () => {
  it('should navigate to detail screen when product is tapped', async () => {
    const { getByText, findByText } = renderWithNavigation(
      <ProductList products={mockProducts} />
    );
 
    fireEvent.press(getByText('Running Shoes'));
    const detailTitle = await findByText('Running Shoes Details');
    expect(detailTitle).toBeTruthy();
  });
});

Deep linking allows users to navigate directly to specific screens via URLs. React Navigation v5 supports deep linking through the linking configuration:

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Main: {
        screens: {
          Home: {
            screens: {
              ProductList: 'products',
              ProductDetail: 'products/:id',
            },
          },
          Search: 'search/:query?',
          Profile: 'profile/:userId',
        },
      },
      Auth: {
        screens: {
          Login: 'login',
          Register: 'register',
          ForgotPassword: 'forgot-password',
        },
      },
      Modal: 'modal/:title',
    },
  },
  // Custom function to parse deep link parameters
  async getInitialURL() {
    // Check if app was opened from a deep link
    const url = await Linking.getInitialURL();
    if (url != null) return url;
 
    // Check for push notification deep links
    const message = await messaging().getInitialNotification();
    return message?.data?.deepLink;
  },
  // Subscribe to incoming deep links
  subscribe(listener) {
    const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
      listener(url);
    });
 
    const unsubscribeNotification = messaging().onNotificationOpenedApp((message) => {
      const url = message?.data?.deepLink;
      if (url) listener(url);
    });
 
    return () => {
      linkingSubscription.remove();
      unsubscribeNotification();
    };
  },
};

Configure universal links for seamless web-to-app navigation:

// ios/myapp/AssociatedDomains.entitlements
// <key>com.apple.developer.associated-domains</key>
// <array>
//   <string>applinks:myapp.com</string>
// </array>
 
// android/app/src/main/AndroidManifest.xml
// <intent-filter android:autoVerify="true">
//   <action android:name="android.intent.action.VIEW" />
//   <category android:name="android.intent.category.DEFAULT" />
//   <category android:name="android.intent.category.BROWSABLE" />
//   <data android:scheme="https" android:host="myapp.com" />
// </intent-filter>
 
// Testing deep links in development
// iOS: xcrun simctl openurl booted "myapp://products/123"
// Android: adb shell am start -a android.intent.action.VIEW -d "myapp://products/123"

Persist navigation state so users return to where they left off:

import AsyncStorage from '@react-native-async-storage/async-storage';
 
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));
        }
      } finally {
        setIsReady(true);
      }
    };
 
    if (!isReady) {
      restoreState();
    }
  }, [isReady]);
 
  if (!isReady) return <LoadingScreen />;
 
  return (
    <NavigationContainer
      initialState={initialState}
      onStateChange={(state) => {
        AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
      }}
      linking={linking}
    >
      <AppNavigator />
    </NavigationContainer>
  );
}

Conditional Navigation Based on Auth State

Implement authentication-aware navigation that redirects unauthenticated users:

function useAuthNavigation() {
  const { isAuthenticated, isLoading } = useAuth();
  const navigation = useNavigation();
 
  useEffect(() => {
    if (isLoading) return;
 
    if (!isAuthenticated) {
      // Reset to auth stack
      navigation.reset({
        index: 0,
        routes: [{ name: 'Auth' }],
      });
    }
  }, [isAuthenticated, isLoading, navigation]);
 
  return { isAuthenticated, isLoading };
}
 
// In your root navigator
function AppNavigator() {
  const { isAuthenticated } = useAuth();
 
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      {isAuthenticated ? (
        <Stack.Screen name="Main" component={MainTabNavigator} />
      ) : (
        <Stack.Screen name="Auth" component={AuthNavigator} />
      )}
    </Stack.Navigator>
  );
}

React Navigation v5 provides lifecycle hooks for screen-level data loading:

import { useFocusEffect, useIsFocused } from '@react-navigation/native';
 
function ProductListScreen() {
  const [products, setProducts] = useState([]);
  const isFocused = useIsFocused();
 
  // Refetch data when screen gains focus
  useFocusEffect(
    useCallback(() => {
      let isActive = true;
 
      fetchProducts().then((data) => {
        if (isActive) {
          setProducts(data);
        }
      });
 
      return () => {
        isActive = false; // Cleanup if screen loses focus before fetch completes
      };
    }, [])
  );
 
  // Use isFocused for conditional rendering
  if (!isFocused) return null;
 
  return <ProductList data={products} />;
}

Performance Optimization

Memory Management for Large Navigation Stacks

// Unmount screens that are far from the current screen
<Stack.Navigator
  detachInactiveScreens={true}
  screenOptions={{
    freezeOnBlur: true, // Freeze inactive screens to save memory
  }}
>
  <Stack.Screen name="ProductList" component={ProductListScreen} />
  <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
</Stack.Navigator>

Avoiding Unnecessary Re-renders

// Memoize screen components to prevent re-renders during navigation transitions
const ProductScreen = React.memo(function ProductScreen() {
  const route = useRoute();
  const { productId } = route.params;
 
  return <ProductContent id={productId} />;
});
 
// Use stable references for navigation params
function useStableParams<T>(params: T): T {
  const ref = useRef(params);
  if (JSON.stringify(ref.current) !== JSON.stringify(params)) {
    ref.current = params;
  }
  return ref.current;
}

Deep Linking Patterns for Real Apps

Deep linking is critical for user acquisition, retention, and push notification handling. React Navigation's linking configuration maps URLs to screens, but real-world patterns require handling authentication state, nested navigators, and URL parameters.

Auth-Aware Deep Linking

Most apps need to redirect unauthenticated users to login when they open a deep link. React Navigation handles this elegantly with conditional navigation:

const linking = {
  prefixes: ["myapp://", "https://myapp.com"],
  config: {
    screens: {
      Auth: {
        screens: {
          Login: "login",
          Register: "register",
        },
      },
      Main: {
        screens: {
          Home: "home",
          Product: {
            path: "product/:id",
            parse: {
              id: (id: string) => id,
            },
          },
          Order: {
            path: "order/:orderId",
            parse: {
              orderId: (id: string) => id,
            },
          },
        },
      },
    },
  },
  async getInitialURL() {
    return await Linking.getInitialURL();
  },
  subscribe(listener: (url: string) => void) {
    const sub = Linking.addEventListener("url", ({ url }) => listener(url));
    return () => sub.remove();
  },
};

Push Notification Navigation

When users tap a push notification, navigate to a specific screen by storing the pending target and applying it once the app is ready:

import { createNavigationContainerRef } from "@react-navigation/native";
 
export const navigationRef = createNavigationContainerRef();
let pendingNavigation: { screen: string; params?: Record<string, any> } | null = null;
 
export function handleNotificationOpen(notification: any) {
  const { screen, params } = notification.data;
  if (navigationRef.isReady()) {
    navigationRef.navigate(screen as never, params as never);
  } else {
    pendingNavigation = { screen, params };
  }
}
 
export function applyPendingNavigation() {
  if (pendingNavigation && navigationRef.isReady()) {
    navigationRef.navigate(
      pendingNavigation.screen as never,
      pendingNavigation.params as never
    );
    pendingNavigation = null;
  }
}

Shared Element Transitions

For polished transitions between screens (like tapping a product image to see full details), React Navigation integrates with react-native-shared-element:

import { SharedElement } from "react-navigation-shared-element";
 
function ProductCard({ product }) {
  return (
    <SharedElement id={`product.${product.id}.image`}>
      <Image source={{ uri: product.image }} style={styles.thumbnail} />
    </SharedElement>
  );
}

Testing Navigation Flows

Testing navigation prevents regressions in user flows:

import { render, fireEvent, waitFor } from "@testing-library/react-native";
import { NavigationContainer } from "@react-navigation/native";
 
function renderWithNavigation(component: React.ReactElement) {
  const Stack = createNativeStackNavigator();
  return render(
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Test" component={() => component} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
 
describe("Product Flow", () => {
  it("navigates to product detail on tap", async () => {
    const { getByTestId, getByText } = renderWithNavigation(
      <ProductList products={mockProducts} />
    );
    fireEvent.press(getByTestId("product-1"));
    await waitFor(() => {
      expect(getByText("Product Details")).toBeTruthy();
    });
  });
});

Future Outlook

React Navigation continues to evolve with React Native's new architecture. The v7 preview (already available) brings first-class support for Fabric, improved TypeScript inference, static API configuration alongside the component API, and deeper integration with Expo Router for file-system based routing. The library remains the de facto standard for React Native navigation, with virtually every React Native app depending on it.

Conclusion

React Navigation v5 transformed React Native navigation from a configuration-heavy chore into a composable, hook-based experience that feels natural to React developers. Its component-based API, TypeScript support, and extensive customization options make it the clear choice for navigation in React Native applications.

Key takeaways:

  1. Navigators are React components — composable, configurable, and familiar
  2. Hooks replace class-based navigation — useNavigation, useRoute, useFocusEffect
  3. TypeScript types catch navigation errors at compile time — define params upfront
  4. Nesting navigators creates complex flows — stacks inside tabs inside drawers
  5. Deep linking is built-in — configure once, works on iOS and Android
  6. Performance is tunable — lazy loading, memoization, and selective unmounting
  7. The ecosystem is massive — community libraries, templates, and guides everywhere

Install React Navigation v5 today with npm install @react-navigation/native react-native-screens react-native-safe-area-context and build fluid, native-feeling navigation for your React Native app.