blog bg

July 11, 2024

Introduction to React Native Navigation with TypeScript

Share what you learn in this blog to prepare for your interview, create your forever-free profile now, and explore how to monetize your valuable knowledge.

INTRODUCTION

Navigation is the ability to move from screen to screen within an application. In this post, we will learn how to use React Native's provided APIs to switch and move between different screens, add tabs to screens, and combine different navigation patterns.

 

Setting Up React Native with TypeScript

First, install React Native with TypeScript:

npx create-expo-app --template

Make sure to select the blank TypeScript template.

 

After installation, install React Navigation:

npm install @react-navigation/native @react-navigation/native-stack 

Here, we installed the React Navigation stack and drawer, which is a pattern of navigation in React Native.

 

Understanding Navigation

In a web browser, you can link to different pages using an anchor (<a>) tag. The URL is pushed to the browser history stack when the user clicks on a link. When the user presses the back button, the browser pops the item from the top of the history stack, so the active page is now the previously visited page.

React Native doesn't have a built-in idea of a global history stack like a web browser does -- this is where React Navigation enters the story. React Navigation's native stack navigator allows your app to transition between screens and manage navigation history.

 

Starting the Project

To start the project, run:

npm start

 

Working with Stack Navigator

First, create a screens directory and then create a Home.tsx file:

 


import React from 'react';
import { Text, View } from 'react-native';
type Props = {};
function Home({}: Props) {
 return (
   <View>
     <Text>Home Screen</Text>
   </View>
 );
}
export default Home;

Don't forget to add your own styles. Next, move to your App.tsx file. The React Native stack exposes a method called createNativeStackNavigator that returns an object containing two properties: the Screen and Navigator properties. These are both React Native components used for configuring the navigator. The Navigator should contain the Screen component as its children, and the Screen component is used to define the application routes (the different screens).

 

The NavigatorContainer is a component that manages our navigation tree and contains the navigation state. The NavigatorContainer component must wrap all navigators.

import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from "@react-navigation/native-stack";
export type RootStackParamList = {
 Home: undefined;
};
function HomeScreen() {
 return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
     <Text>Home Screen</Text>
   </View>
 );
}
const Stack = createNativeStackNavigator<RootStackParamList>();
function App() {
 return (
   <NavigationContainer>
     <Stack.Navigator initialRouteName="Home">
       <Stack.Screen name="Home" component={HomeScreen} />
     </Stack.Navigator>
   </NavigationContainer>
 );
}
export default App;

The RootStackParamList type defines the route names in the stack as keys and the prop types for the components as their values. The Home route is typed as undefined because no props are expected to be passed to the component. If you check your app, you can see a bar with the name "Home". The styles you see for the navigation bar and the content area are the default configuration for a stack navigator. We'll learn how to configure those later.

 

Adding Another Screen

Create another screen called Details.tsx in your screens directory:


import React from "react";
import { StyleSheet, Text, View } from "react-native";
type Props = {};
function Details({}: Props) {
 return (
   <View style={styles.container}>
     <Text>Details Screen</Text>
   </View>
 );
}
export default Details;
const styles = StyleSheet.create({
 container: {
   justifyContent: "center",
   alignItems: "center",
   flex: 1,
 },
});

 

Update your App.tsx file:


import { StatusBar } from "expo-status-bar";
import { StyleSheet } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import Home from "./screens/Home";
import Details from "./screens/Details";

export type RootStackParamList = {
 Home: undefined;
 Details: undefined;
};

const Stack = createNativeStackNavigator<RootStackParamList>();

export default function App() {
 return (
   <>
     <StatusBar />
     <NavigationContainer>
       <Stack.Navigator initialRouteName="Home">
         <Stack.Screen name="Home" component={Home} />
         <Stack.Screen name="Details" component={Details} />
       </Stack.Navigator>
     </NavigationContainer>
   </>
 );
}
const styles = StyleSheet.create({});

 

 Route Component Props

All of the route configuration is specified as props to our navigator. The Screen component accepts a name prop, which is the name of the route and will also be used to navigate, and a component prop, which is the component the route will render. You can change the initial route's name on the Navigator to Details and reload the app.

 

The Screen component also accepts an options prop where you can add more configuration to the screen:

 

 

export default function App() {
 return (
   <>
     <StatusBar />
     <NavigationContainer>
       <Stack.Navigator initialRouteName="Home">
         <Stack.Screen
           name="Home"
           component={Home}
           options={{ headerShown: false }}
         />
         <Stack.Screen
           name="Details"
           component={Details}
           options={{ title: "My Details" }}
         />
       </Stack.Navigator>
     </NavigationContainer>
   </>
 );
}

Here, we removed the header in the Home screen and changed the title of the Details screen. You can look at the docs for more possible options https://reactnavigation.org/docs/screen-options/.

 

Moving Between Screens

Now that we have our navigation set up, let's learn how to move between screens.

Each route component automatically has the navigation and route objects passed to them as props. We can use the navigation object to navigate to different screens:

 

import React from 'react';
import { View, Text, Button } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../App';

type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;

function Home({ navigation }: Props) {
 return (
   <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
     <Text>Home Screen</Text>
     <Button
       title="Move To Details Page"
       onPress={() => navigation.navigate('Details')}
     />
   </View>
 );
}
export default Home;

 

Now, when the button is clicked, we call the navigate function in the navigation object to navigate to the Details page. React Native provides a button to navigate back to the Home screen.

 

Passing Parameters to Routes

We can achieve this by adding parameters in the navigate method as an object:

function Home({ navigation }: Props) {
 return (
   <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
     <Text>Home Screen</Text>
     <Button
       title="Move To Details Page"
       onPress={() => navigation.navigate('Details', { detailId: 'One' })}
     />
   </View>
 );
}

 

We need to update the RootStackParamList type in our App.tsx` so that TypeScript is aware that the Details component is expecting a detailId param:

export type RootStackParamList = {
 Home: undefined;
 Details: { detailId: string };
};

 

Now, in our Details component:

import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../App';

type Props = NativeStackScreenProps<RootStackParamList, 'Details'>;

function Details({ route }: Props) {
 const { detailId } = route.params;
 return (
   <View style={styles.container}>
     <Text>Details Screen</Text>
     <Text>{detailId}</Text>
   </View>
 );
}
export default Details;

const styles = StyleSheet.create({
 container: {
   flex: 1,
   justifyContent: 'center',
   alignItems: 'center',
 },
});

 

We can also set initial parameters for our components if the params are not passed in the navigation:

 

export type RootStackParamList = {
 Home: undefined;
 Details: { detailId: string; optional?: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

export default function App() {
 return (
   <>
     <StatusBar />
     <NavigationContainer>
       <Stack.Navigator initialRouteName="Home">
         <Stack.Screen
           name="Home"
           component={Home}
           options={{ headerShown: false }}
         />
         <Stack.Screen
           name="Details"
           component={Details}
           options={{ title: 'My Details' }}
           initialParams={{ optional: 'This was Passed from initial param' }}
         />
       </Stack.Navigator>
     </NavigationContainer>
   </>
 );
}

 

In Details.tsx:

function Details({ route }: Props) {
 const { detailId, optional } = route.params;
 return (
   <View style={styles.container}>
     <Text>Details Screen</Text>
     <Text>{detailId}</Text>
     <Text>{optional}</Text>
   </View>
 );
}

 

Updating Params

You can also update the screens parameters by calling the setParams method in the navigation. Add a button to the Details screen to update the optional param:

function Details({ navigation, route }: Props) {
 const { detailId, optional } = route.params;
 
 return (
   <View style={styles.container}>
     <Text>Details Screen</Text>
     <Text>{detailId}</Text>
     <Text>{optional}</Text>
     <Button
       title="Update Param"
       onPress={() => navigation.setParams({ optional: 'Updated' })}
     />
   </View>
 );
}

 

Setting Screen Options Dynamically

You can set the screen options using the setOptions method in the navigation object. Use a useLayoutEffect hook to update the title of the screen on load:

import React, { useLayoutEffect } from 'react';

function Details({ navigation, route }: Props) {
 const { detailId, optional } = route.params;
 
 useLayoutEffect(() => {
   navigation.setOptions({ title: `Details: ${detailId}` });
 }, [detailId, navigation]);
 
 return (
   <View style={styles.container}>
     <Text>Details Screen</Text>
     <Text>{detailId}</Text>
     <Text>{optional}</Text>
     <Button
       title="Update Param"
       onPress={() => navigation.setParams({ optional: 'Updated' })}
     />
   </View>
 );
}

 

Nesting Navigators

Nesting navigators means rendering a navigator inside a screen of another navigator. This is similar to nested routes in React Router DOM.

import React from "react";
import { RootStackParamList } from "../App";
import {
 createNativeStackNavigator,
 NativeStackScreenProps,
} from "@react-navigation/native-stack";
import Feed from "./Feed";
import Messages from "./Messages";

type HomeStackParamList = {
 Feed: undefined;
 Messages: undefined;
};

type Props = NativeStackScreenProps<RootStackParamList, "Home">;
const Tab = createNativeStackNavigator<HomeStackParamList>();
function Home({}: Props) {
 return (
   <Tab.Navigator initialRouteName="Feed">
     <Tab.Screen name="Feed" component={Feed} />
     <Tab.Screen name="Messages" component={Messages} />
   </Tab.Navigator>
 );
}
export default Home;


In the next post, we will learn how to use the drawer navigation.

473 views

Please Login to create a Question