React Native is one of the most popular cross-platform app development frameworks. Using JavaScript, you can develop native apps for both Android and iOS.

One important part of creating apps is being able to navigate between screens. In this tutorial, you'll learn how to use React Navigation in your app to add navigation. This will include basic navigation from one screen to another, going back to the previous screen, passing parameters, listening to events, and more.

This tutorial will not explain the basics of creating a React Native app. If you're interested in learning about that, you can check out my other tutorial React Native Tutorial: Create Your First App.

You can find the code for this tutorial on this GitHub repository.

Prerequisites

Make sure you have both Node.js and NPM installed on your machine. You can check if they're installed with the following command:

node -v
npm -v

If they're not installed, you can install Node.js and NPM will be installed automatically with it.

After that, make sure you install Expo's CLI if you don't have it installed:

npm install -g expo-cli

If you're not familiar with what Expo is, Expo provides a set of tools built around React. Most importantly, it allows you to write and build your React Native app in minutes.

You should also install Expo Go on your device to be able to test the app as we go through the tutorial.

Project Setup

Start by creating a new React Native project using Expo's CLI:

expo init react-navigation-tutorial —npm

You'll be then prompted to choose the type of app, choose "blank".

After that the init command will install the dependencies you need for your React Native project.

Once it's done, change to the directory of the project:

cd react-navigation-tutorial

Now, you'll install the dependencies required to use React Navigation. First, install the building blocks libraries using Expo's CLI with the following command:

expo install react-native-screens react-native-safe-area-context

Second, install the native stack navigator library:

npm install @react-navigation/native-stack

Now, you have all the libraries required to start using React Navigation.

Finally, install React Native Paper to have beautifully-styled components in your app:

npm install react-native-paper

Set Up React Navigation

Before you can start adding routes to your app, you need to create a Stack Navigator. This stack navigator will be used to create screens and navigate between them.

In App.js, change the content to the following:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Provider as PaperProvider } from 'react-native-paper';
import HomeScreen from './screens/HomeScreen';

const Stack = createNativeStackNavigator()

export default function App() {
  return (
    <PaperProvider>
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
        </Stack.Navigator>
    </NavigationContainer>
    </PaperProvider>
  );
}

Notice that in the returned value of App, you first have <PaperProvider> at the top of the elements. This is just used to make sure all screens share the same theme.

Next, you have the <NavigationContainer>. This component manages the navigation tree and contains the navigation state. So, it should wrap Stack navigators.

Inside <NavigationContainer> you have <Stack.Navigator>. Stack is created using createNativeStackNavigator. This function returns an object containing 2 properties: Screen and Navigator. The Navigator property is a React element and it should be used to wrap the Screen elements to manage routing configurations.

So, inside <Stack.Navigator> you'll have one or more <Stack.Screen> components. For each route, you'll have a <Stack.Screen> component. Now, you just have one component that points to the Home route.

Notice that a Screen component takes the name prop, which is the name of the route and will be referenced later on when navigating to this route, and the component property which is the component to render for this route.

As you can see, you're passing HomeScreen component to the Home route, so you need to create it now.

Create the directory screens, and inside that directory create a new file HomeScreen.js with the following content:

import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { Button, Card } from 'react-native-paper';
import { DefaultTheme } from 'react-native-paper';

function HomeScreen () {
  return (
    <ScrollView style={styles.scrollView}>
      <Card style={styles.card}>
        <Card.Title title="Home Screen" />
      </Card>
    </ScrollView>
  )
}

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: DefaultTheme.colors.background,
    paddingTop: 10
  },
  card: {
    width: '90%',
    marginLeft: 'auto',
    marginRight: 'auto'
  }
});

export default HomeScreen

This will create a basic home screen with a ScrollView component containing a Card component with the title "Home Screen".

You just created your first route with React Navigation! Let's test it out.

In your terminal run to start your app:

npm start

Then, you'll be able to run the app on Expo Go on your phone by scanning the QR code on the Expo Go app on Android or on the Camera app on iOS.

Once the app run, you'll see the Home screen that you created as it's the default and only route.

In this section, you'll learn how to navigate from one screen to another.

Screens that are inside the Stack Navigation, like HomeScreen in this app, receive a navigation prop. This prop allows us to navigate to other screens in the app.

Let's first create the screen you'll navigate to. Create the file screens/BookScreen.js with the following content:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Card } from 'react-native-paper';
import { DefaultTheme } from 'react-native-paper';

function BookScreen () {
  return (
    <View style={styles.container}>
      <Card style={styles.card}>
        <Card.Title title="This is Books Screen" />
      </Card>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: DefaultTheme.colors.background,
    alignItems: 'center',
    paddingTop: 10
  },
  card: {
    width: '90%'
  }
});

export default BookScreen;

This screen is similar to HomeScreen. It just shows a card with the title "This is Books Screen".

Next, add the new route in App.js for BookScreen below the "Home" route:

<Stack.Screen name="Book" component={BookScreen} />

Don't forget to import BookScreen below the imports at the beginning of App.js:

import BookScreen from './screens/BookScreen';

Now, you can navigate to the "Book" route inside this stack navigation. To see how it works, you'll add a button in HomeScreen that when the user clicks will navigate to BookScreen.

In HomeScreen, add the navigation prop to the function:

function HomeScreen ({ navigation })

Then, change the returned value of the function to the following:

return (
    <ScrollView style={styles.scrollView}>
      <Card style={styles.card}>
        <Card.Title title="Navigate to 'Book' Screen" />
        <Card.Content>
          <Button mode="contained" onPress={() => navigation.navigate('Book')}>
            Navigate
          </Button>
        </Card.Content>
      </Card>
    </ScrollView>
  )

You change the title of the card to "Navigate to 'Book' Screen". Then, inside the content of the card, you add a Button component with onPress listener. This listener will be executed when the user presses on the button.

Inside the listener, you use the navigation prop to navigate to the Book screen using the method navigate. The method navigate takes the name of the route to navigate to.

Let's test it out. Run the app again if it isn't running. You should see a new button on the Home screen.

Try clicking on the Navigate button. You'll be navigated to the BookScreen.

Notice how the back button in the navigation bar is added automatically when you navigate to another screen. You can use it to go back to the previous screen, which is in this case the Home screen.

In a lot of cases, you'll need to navigate to another screen and pass it parameters. In this section, you'll create an input that allows the user to enter their name, then when they submit you'll open a new screen called NameScreen which will show a greeting to the user with the name they entered.

Start by adding a new state variable to hold the value of the input you'll add soon in HomeScreen.js:

const [name, setName] = useState('');

Make sure to add an import for useState at the beginning of the file:

import React, { useState } from 'react';

Then, add a new Card component below the existing one:

<Card style={styles.card}>
    <Card.Title title="Navigate with Parameters" />
    <Card.Content>
        <TextInput mode="outlined" label="Name" value={name} onChangeText={(text) => setName(text)} style={styles.textInput} />
            <Button mode="contained" disabled={name.length === 0} onPress={() => navigation.navigate('Name', { name })}>
                Navigate
    	</Button>
    </Card.Content>
</Card>

This new card has a TextInput with the value name, and on the onChangeText event, which occurs when the user makes changes to the text inside the input, you change the value of the name state variable.

Below the input, you have a button that is disabled when the name state variable is empty. You also add an onPress event listener which navigates the user to the Name route.

Notice that you're using the same navigation.navigate method that you used before to navigate to the "Book" route, however, this time you pass a second parameter to the method. This method accepts as a second parameter an object of parameters you want to pass. Here, you pass the name variable inside the object.

One last thing you need to do in the HomeScreen is change the styles object to the following:

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: DefaultTheme.colors.background,
    paddingTop: 10
  },
  card: {
    width: '90%',
    marginLeft: 'auto',
    marginRight: 'auto',
    marginBottom: 10
  },
  textInput: {
    marginBottom: 10
  }
});

This just makes some changes to the card property and adds a new property textInput.

You'll now create the NameScreen which will be the component for the "Name" route you'll add soon. Create screens/NameScreen.js with the following content:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Card } from 'react-native-paper';
import { DefaultTheme } from 'react-native-paper';

function NameScreen ({ route }) {
  const { name } = route.params;

  return (
    <View style={styles.container}>
      <Card style={styles.card}>
        <Card.Title title={`Hello, ${name}`} />
      </Card>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: DefaultTheme.colors.background,
    alignItems: 'center',
    paddingTop: 10
  },
  card: {
    width: '90%'
  }
});

export default NameScreen;

This screen is essentially the same as BookScreen, however, notice the route prop passed to the function.

As mentioned before, the components inside the navigation stack receive a navigation prop to navigate between screens. They also receive a route prop which you can use to get the parameters passed to the screen using route.params.

So, in NameScreen, you retrieve the value entered in the Name input in the HomeScreen, which is passed when navigating to the NameScreen when the button is clicked using route.params:

const { name } = route.params;

You then show the user a greeting inside a card using the name parameter.

One last thing left to do is add the "Name" route to the navigation stack. Add a new <Stack.Screen> below the previous ones in App.js:

<Stack.Screen name="Name" component={NameScreen} />

And import NameScreen below other imports at the beginning of the file:

import NameScreen from './screens/NameScreen';

Let's test it out. Run the app if it isn't already running. You'll see that there's a new card on the Home screen with an input and a button.

Try entering your name, then click the "Navigate" button below the input. You'll be navigated to the NameScreen and you'll see your name.

You can try going back then changing the value of the name. The name in the NameScreen will change accordingly.

Configure the Header

Change the title

Sometimes, you want the title of a route to be different than the name of the route, or you want the title to be dynamic based on a parameter passed or some other logic.

In this section, you'll add a new input in HomeScreen that will let you to enter a title, then on clicking the navigate button you'll be navigated to a new screen TitleScreen, whose title will change based on the title entered in the input in HomeScreen.

Start by adding a new state variable in HomeScreen.js to store the title:

const [title, setTitle] = useState('');

Then, add a new card below the previous cards:

<Card style={styles.card}>
        <Card.Title title="Navigate to Route with Title" />
        <Card.Content>
          <TextInput mode="outlined" label="Title" value={title} onChangeText={(text) => setTitle(text)} style={styles.textInput} />
          <Button mode="contained" disabled={title.length === 0} onPress={() => navigation.navigate('Title', { title })}>
            Navigate
          </Button>
        </Card.Content>
      </Card>

This is pretty much the same as the card we added in the previous section. The only difference is that it uses the title state variable, and the listener for onPress for the button navigates to the route "Title", passing the title as a parameter.

Next, create the file screens/Title.js with the following content:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Card } from 'react-native-paper';
import { DefaultTheme } from 'react-native-paper';

function TitleScreen () {
  return (
    <View style={styles.container}>
      <Card style={styles.card}>
        <Card.Title title="This is title screen" />
      </Card>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: DefaultTheme.colors.background,
    alignItems: 'center',
    paddingTop: 10
  },
  card: {
    width: '90%'
  }
});

export default TitleScreen;

This screen has nothing special. It just shows a card with the title "This is title screen". It doesn't even use the title parameter.

That's because you can change the title of a screen by passing an options parameter to <Stack.Screen> when adding the route.

In App.js, add an import for TitleScreen below other imports at the beginning of the file:

import TitleScreen from './screens/TitleScreen';

Then, add the new route below previous <Stack.Screen>:

<Stack.Screen name="Title" component={TitleScreen} options={({route}) => ({title: route.params.title})} />

Every Stack.Screen component can take an options prop. The options prop can be an object like so:

options={{title: "Hello"}}

This is enough if your options are static. However, if the value for any of the options is dynamic, like in this example, you can instead pass it a function that returns an object. This function can receive as a parameter route, which is similar to the route parameter you used in NameScreen:

options={({route}) => ({title: route.params.title})}

In this function, you return an object that has a title property with the value route.params.title. So, when the user enters a title in the input and clicks on the Navigate button, the title will be passed to the route, which will change the title of "Title" route using the function passed to the options prop.

Let's test it out. Run the app if it's not running already. You should see a new card on the Home screen with a new input:

Try entering any title you want in the input then click on Navigate. You'll see that you'll be navigated to the TitleScreen, but the title will be the value you entered in the input.

You can try going back then changing the value entered in the input. The title of the screen will change accordingly.

Add Header Button

Another option you can add is header buttons. You can customize what your header shows on the left and right by using the headerRight and headerLeft properties in the options param of <Stack.Screen>.

In this section, you'll add a button to the right of the header of the Home screen which will show a user an alert when they click on it.

In App.js, add to the "Home" route a prop option:

<Stack.Screen name="Home" component={HomeScreen} options={{
            headerRight: () => (
              <IconButton icon="alert-outline" onPress={() => alert('You\'re awesome!')} color={DefaultTheme.colors.notification} />
            )
}} />

In the options prop, you pass an object with the property headerRight. The headerRight property can be a component that you can render elements inside. You render an IconButton component, which is a button that only has an icon without text. In the onPress event listener, you show an alert to the user.

That's all. Run the app if it isn't running already. You'll see a new icon at the top right of the header in Home Screen.

Click on it and you'll see an alert.

In React Navigation, you can subscribe to two events: focus and blur. The focus event will occur every time the screen is opened and is currently the main screen. The blur event will occur whenever a screen was in focus but navigation occurs that makes the screen not the current main screen. Simply put, the focus event is when the screen is visible to the user, and the blur event occurs when a screen was visible to a user but isn't anymore.

In this section, you'll add a counter that will count how many times you navigated to other screens from the home screen. So, this counter will increment in the blur event.

To subscribe to events, in a useEffect callback inside a component, you can use navigation.addListener passing it the name of the event as a first parameter, and the event handler as a second parameter. The addListener method returns an unsubscribe function which you should return in the useEffect callback.

In HomeScreen.js, add an import for useEffect at the beginning of the file:

import React, { useEffect, useState } from 'react';

Then, add a new state variable counter inside the HomeScreen component:

const [counter, setCounter] = useState(0);

After that add the useEffect callback that will register the event handle for the blur event:

useEffect(() => {
    return navigation.addListener('blur', () => {
      setCounter(counter + 1);
    });
  });

As you can see, you use navigation.addListener to add an event listener to the blur event. The function passed as a second parameter is the handler of the event. It will be executed whenever the screen is out of focus (not the screen visible to the user). Inside the event handler, you increment the counter by 1.

Finally, you'll show the counter at the top of the screen. In ScrollView, add a new card before the previously added cards:

<Card style={styles.card}>
	<Card.Title title={`Navigation Count: ${counter}`} />
</Card>

Let's test it out. Run the app if it's not already running. You should see a card with a count of navigation at the top of the screen.

Go Back

The last thing you'll learn in this tutorial is how to go back to the previous screen. As you've seen throughout the tutorial, when you navigate to a screen you'll see a back button in the header which you can use to go back to the previous screen.

Although this is sufficient in most cases, sometimes you want to go back to the previous screen programmatically. You'll create in this section a new screen BackScreen which you can go to from the HomeScreen. This screen will have a back button in its content and when you click on it you can go back to the previous screen, which is the HomeScreen.

Create the file screens/BackScreen.js with the following content:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Button, Card } from 'react-native-paper';
import { DefaultTheme } from 'react-native-paper';

function BackScreen ({ navigation }) {
  return (
    <View style={styles.container}>
      <Card style={styles.card}>
        <Card.Title title="This is Back Screen" />
        <Card.Content>
          <Button mode="contained" onPress={() => navigation.goBack()}>
            Go Back
          </Button>
        </Card.Content>
      </Card>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: DefaultTheme.colors.background,
    alignItems: 'center',
    paddingTop: 10
  },
  card: {
    width: '90%'
  }
});

export default BackScreen;

This screen is pretty much the same as other screens. The things you should note is that, first, the screen has uses the navigation prop that is passed to components inside the navigation stack. Second, the screen has a button inside a card which, on press, will use the navigation prop to go back using the goBack method:

<Button mode="contained" onPress={() => navigation.goBack()}>

Now, go to the HomeScreen and add a new card at the end of the ScrollView:

<Card style={styles.card}>
    <Card.Title title="Navigate to 'Back' Screen" />
    <Card.Content>
        <Button mode="contained" onPress={() => navigation.navigate('Back')}>
            Navigate
    	</Button>
    </Card.Content>
</Card>

This card just shows a button to navigate to the route "Back". Let's add that route now.

In App.js add a new <Stack.Screen> component below the previously added ones:

<Stack.Screen name="Back" component={BackScreen} />

It's ready now. Run the app if it isn't already running. You'll see a new card at the end of the list.

Click on "Navigate" on the last card. You'll be navigated to the BackScreen.

Click on the "Go Back" button in the card. You'll be taken back to the HomeScreen.

Conclusion

In this tutorial, you learned how to use React Navigation to navigate between screens, pass a parameter from one screen to another, configure the header of a screen, add navigation event listeners, and go back programmatically to the previous screen.

Although this covers most use cases for using React Navigation, be sure to check out their documentation for more details on how to use it.