This article was originally published on my personal blog
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.
Navigate to Another Screen
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.
Navigate with Parameters
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.
Navigation Events
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.