DailyDevDiet

logo - dailydevdiet

Learn. Build. Innovate. Elevate your coding skills with dailydevdiet!

Chapter 7: React Native Navigation

React Native Navigation

Navigation is a crucial aspect of any mobile application, allowing users to move between different screens and sections of your app. React Native doesn’t include a built-in navigation solution, but the community has developed excellent navigation libraries that provide powerful and flexible navigation systems.

Introduction to React Native Navigation

Navigation in mobile apps typically involves:

  • Moving between screens
  • Passing data between screens
  • Managing navigation history
  • Providing navigation UI elements (headers, tabs, drawers)

Types of Navigation Patterns

  1. Stack Navigation: Screens are stacked on top of each other
  2. Tab Navigation: Multiple tabs at the bottom or top
  3. Drawer Navigation: Side menu that slides in
  4. Modal Navigation: Screens that appear on top of current content

React Navigation – The Standard Solution

React Navigation is the most popular navigation library for React Native. It’s JavaScript-based, which means it’s cross-platform and highly customizable.

Installation

npm install @react-navigation/native

# Install dependencies
npm install react-native-screens react-native-safe-area-context

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

Basic Setup

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Stack Navigator

Stack navigation is the most common navigation pattern where screens are stacked on top of each other.

Installing Stack Navigator

npm install @react-navigation/native-stack

Basic Stack Navigator Example

// screens/HomeScreen.js
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

function HomeScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
      <Button
        title="Go to Details with Params"
        onPress={() =>
          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          })
        }
      />
    </View>
  );
}

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

export default HomeScreen;

// screens/DetailsScreen.js
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

function DetailsScreen({ route, navigation }) {
  const { itemId, otherParam } = route.params || {};

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Details Screen</Text>
      <Text style={styles.text}>Item ID: {JSON.stringify(itemId)}</Text>
      <Text style={styles.text}>Other Param: {JSON.stringify(otherParam)}</Text>
      <Button title="Go to Details... again" onPress={() => navigation.push('Details')} />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
      <Button
        title="Go back to first screen in stack"
        onPress={() => navigation.popToTop()}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 16,
    marginBottom: 10,
  },
});

export default DetailsScreen;

Navigation Methods

// Navigate to a new screen
navigation.navigate('ScreenName');

// Push a new screen (even if it's the same)
navigation.push('ScreenName');

// Go back to previous screen
navigation.goBack();

// Go back to the first screen in the stack
navigation.popToTop();

// Replace current screen
navigation.replace('ScreenName');

// Reset navigation state
navigation.reset({
  index: 0,
  routes: [{ name: 'Home' }],
});

Tab Navigator

Tab navigation provides easy access to different sections of your app through tabs.

Installing Tab Navigator

npm install @react-navigation/bottom-tabs

Bottom Tab Navigator Example

// navigation/TabNavigator.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons'; // or react-native-vector-icons

import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
import ProfileScreen from '../screens/ProfileScreen';

const Tab = createBottomTabNavigator();

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

          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Settings') {
            iconName = focused ? 'settings' : 'settings-outline';
          } else if (route.name === 'Profile') {
            iconName = focused ? 'person' : 'person-outline';
          }

          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: 'tomato',
        tabBarInactiveTintColor: 'gray',
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
    </Tab.Navigator>
  );
}

export default TabNavigator;

Customizing Tab Bar

// Advanced tab bar customization
<Tab.Navigator
  screenOptions={{
    tabBarStyle: {
      backgroundColor: '#f0f0f0',
      borderTopWidth: 0,
      elevation: 0,
      shadowOpacity: 0,
      height: 60,
    },
    tabBarLabelStyle: {
      fontSize: 12,
      fontWeight: 'bold',
    },
    tabBarIconStyle: {
      marginTop: 5,
    },
  }}
>

Top Tab Navigator

npm install @react-navigation/material-top-tabs react-native-tab-view
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';

const Tab = createMaterialTopTabNavigator();

function TopTabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={{
        tabBarLabelStyle: { fontSize: 12 },
        tabBarStyle: { backgroundColor: 'powderblue' },
        tabBarIndicatorStyle: { backgroundColor: 'blue' },
      }}
    >
      <Tab.Screen name="First" component={FirstRoute} />
      <Tab.Screen name="Second" component={SecondRoute} />
      <Tab.Screen name="Third" component={ThirdRoute} />
    </Tab.Navigator>
  );
}

Drawer Navigator

Drawer navigation provides a side menu that slides in from the edge of the screen.

Installing Drawer Navigator

npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

Basic Drawer Navigator

// navigation/DrawerNavigator.js
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';

import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
import ProfileScreen from '../screens/ProfileScreen';

const Drawer = createDrawerNavigator();

function DrawerNavigator() {
  return (
    <Drawer.Navigator
      screenOptions={{
        drawerStyle: {
          backgroundColor: '#c6cbef',
          width: 240,
        },
        drawerActiveTintColor: '#e91e63',
        drawerLabelStyle: {
          marginLeft: -25,
          fontSize: 15,
        },
      }}
    >
      <Drawer.Screen
        name="Home"
        component={HomeScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="home-outline" size={22} color={color} />
          ),
        }}
      />
      <Drawer.Screen
        name="Profile"
        component={ProfileScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="person-outline" size={22} color={color} />
          ),
        }}
      />
      <Drawer.Screen
        name="Settings"
        component={SettingsScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="settings-outline" size={22} color={color} />
          ),
        }}
      />
    </Drawer.Navigator>
  );
}

export default DrawerNavigator;

Custom Drawer Content

import { DrawerContentScrollView, DrawerItem } from '@react-navigation/drawer';

function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <View style={styles.drawerHeader}>
        <Text style={styles.drawerHeaderText}>My App</Text>
      </View>
      <DrawerItem
        label="Home"
        onPress={() => props.navigation.navigate('Home')}
        icon={({ color, size }) => (
          <Ionicons name="home-outline" color={color} size={size} />
        )}
      />
      <DrawerItem
        label="Profile"
        onPress={() => props.navigation.navigate('Profile')}
        icon={({ color, size }) => (
          <Ionicons name="person-outline" color={color} size={size} />
        )}
      />
      <DrawerItem
        label="Logout"
        onPress={() => {
          // Handle logout
        }}
        icon={({ color, size }) => (
          <Ionicons name="log-out-outline" color={color} size={size} />
        )}
      />
    </DrawerContentScrollView>
  );
}

// In your drawer navigator
<Drawer.Navigator drawerContent={(props) => <CustomDrawerContent {...props} />}>

Navigation with Parameters

Passing Parameters

// Navigate with parameters
navigation.navigate('Details', {
  itemId: 86,
  otherParam: 'anything you want here',
});

// Navigate with object spread
const item = { id: 86, name: 'Item Name' };
navigation.navigate('Details', { item });

Receiving Parameters

function DetailsScreen({ route, navigation }) {
  const { itemId, otherParam } = route.params;

  return (
    <View>
      <Text>Item ID: {itemId}</Text>
      <Text>Other Param: {otherParam}</Text>
    </View>
  );
}

Updating Parameters

// Update params for current screen
navigation.setParams({
  query: 'search term',
});

// In the screen component
React.useEffect(() => {
  navigation.setParams({ user: updatedUser });
}, [navigation, updatedUser]);

Navigation Headers

Customizing Headers

// Static options
<Stack.Screen
  name="Details"
  component={DetailsScreen}
  options={{
    title: 'Details',
    headerStyle: {
      backgroundColor: '#f4511e',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  }}
/>

// Dynamic options
<Stack.Screen
  name="Details"
  component={DetailsScreen}
  options={({ route }) => ({
    title: route.params.name,
  })}
/>

Header Buttons

function DetailsScreen({ navigation }) {
  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button
          onPress={() => alert('This is a button!')}
          title="Info"
          color="#fff"
        />
      ),
    });
  }, [navigation]);

  return (
    <View>
      <Text>Details Screen</Text>
    </View>
  );
}

Custom Header Component

<Stack.Screen
  name="Details"
  component={DetailsScreen}
  options={{
    header: ({ navigation, route, options }) => (
      <CustomHeader
        title={options.title}
        navigation={navigation}
        route={route}
      />
    ),
  }}
/>

Navigation Guards and Listeners

Navigation Listeners

function HomeScreen({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // Screen came into focus
      console.log('Screen focused');
    });

    return unsubscribe;
  }, [navigation]);

  return <View>...</View>;
}

Navigation Events

function MyScreen({ navigation }) {
  React.useEffect(() => {
    const unsubscribeBlur = navigation.addListener('blur', () => {
      // Screen lost focus
    });

    const unsubscribeBeforeRemove = navigation.addListener('beforeRemove', (e) => {
      // Prevent default behavior
      e.preventDefault();

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

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

  return <View>...</View>;
}

Nested Navigators

Combining different types of navigators for complex navigation structures.

Example: Tab Navigator inside Stack Navigator

// navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import TabNavigator from './TabNavigator';
import LoginScreen from '../screens/LoginScreen';
import DetailsScreen from '../screens/DetailsScreen';

const Stack = createNativeStackNavigator();

function AppNavigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Login"
          component={LoginScreen}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="Main"
          component={TabNavigator}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default AppNavigator;

Deep Linking

Basic Deep Linking Setup

// App.js
const linking = {
  prefixes: ['myapp://'],
  config: {
    screens: {
      Home: 'home',
      Details: 'details/:itemId',
      Profile: {
        path: 'user/:id',
        parse: {
          id: (id) => id.replace(/^@/, ''),
        },
        stringify: {
          id: (id) => `@${id}`,
        },
      },
    },
  },
};

function App() {
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        {/* Your screens */}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Navigation Best Practices

1. Type Safety with TypeScript

// types/navigation.ts
export type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; otherParam?: string };
  Profile: { userId: string };
};

// Use in component
type DetailsScreenProps = StackScreenProps<RootStackParamList, 'Details'>;

function DetailsScreen({ route, navigation }: DetailsScreenProps) {
  const { itemId, otherParam } = route.params;
  // TypeScript knows the exact types now
}

2. Navigation Utils

// utils/navigationUtils.js
import { NavigationActions, StackActions } from '@react-navigation/native';

export const resetToScreen = (navigation, routeName, params = {}) => {
  navigation.dispatch(
    StackActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({ routeName, params })],
    })
  );
};

export const navigateAndReset = (navigation, routeName, params = {}) => {
  navigation.dispatch(
    StackActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({ routeName, params })],
    })
  );
};

3. Performance Optimization

// Lazy loading screens
const HomeScreen = React.lazy(() => import('../screens/HomeScreen'));
const DetailsScreen = React.lazy(() => import('../screens/DetailsScreen'));

// Preload important screens
React.useEffect(() => {
  import('../screens/DetailsScreen');
}, []);

4. 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.log('Navigation error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text>Something went wrong with navigation.</Text>
          <Button title="Restart" onPress={() => this.setState({ hasError: false })} />
        </View>
      );
    }

    return this.props.children;
  }
}

Complete Navigation Example

Here’s a complete example combining multiple navigation patterns:

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';

// Screens
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';
import SettingsScreen from './screens/SettingsScreen';
import ProfileScreen from './screens/ProfileScreen';
import LoginScreen from './screens/LoginScreen';

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

// Home Stack
function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="HomeMain" component={HomeScreen} options={{ title: 'Home' }} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  );
}

// Tab Navigator
function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;
         
          if (route.name === 'HomeTab') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Settings') {
            iconName = focused ? 'settings' : 'settings-outline';
          }
         
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: 'tomato',
        tabBarInactiveTintColor: 'gray',
        headerShown: false,
      })}
    >
      <Tab.Screen name="HomeTab" component={HomeStack} options={{ title: 'Home' }} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

// Main App Navigator
function App() {
  const [isLoggedIn, setIsLoggedIn] = React.useState(false);

  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        {isLoggedIn ? (
          <Stack.Screen name="Main" component={TabNavigator} />
        ) : (
          <Stack.Screen name="Login" component={LoginScreen} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Summary

Navigation is essential for creating intuitive mobile applications. React Navigation provides powerful and flexible navigation solutions including:

  • Stack Navigation for hierarchical screen flow
  • Tab Navigation for quick access to different sections
  • Drawer Navigation for side menu functionality
  • Parameter passing for data flow between screens
  • Header customization for branded navigation experience
  • Deep linking for external navigation integration

Understanding these navigation patterns and their implementation will help you create well-structured and user-friendly React Native applications. In the next chapter, we’ll explore styling in React Native to make your navigable screens look great!

Related Articles:

Scroll to Top