Basics of React Native Stack Navigation
My previous article on navigation in React Native described how to build Tab Navigation. This week, I want to introduce another type of navigation in React Native: Stack Navigation.
Stack Navigation is a system for managing screens similar to a stack of cards, where one screen is placed next to or on top of another. Unlike other types of navigation, Stack Navigation does not inherently display an indicator of all screens. Only you, as the developer, can implement such indicators. For instance, in Tab Navigation, we have a bottom bar showing the available screens, but this is not the case with Stack Navigation.
This navigation type offers developers more customization options, such as altering the appearance and transitions of the screens.
Step-by-Step Implementation
Let's start with a basic example of navigation and two screens:
Creating a Blank Project
I will create the project using the TypeScript blank template. Here's the command:
npx create-expo-app -t expo-template-blank-typescript stack-navigation-example
Adding the Libraries
To run the React Navigation library, we need to add these two essential components to our project:
- react-native-screens- exposes basic navigation components to React Native
- react-native-safe-area-context- provides a safe area for screens, ensuring the screen name or content is positioned correctly
Here's the command:
yarn add react-native-screens react-native-safe-area-context
Then, we can add libraries for building the navigation with React Navigation:
yarn add @react-navigation/native @react-navigation/stack react-native-gesture-handler
-
@react-navigation/native- adds the basic version of the navigation library to the project, which is required for adding any navigation or managing the history of screens in our project
-
@react-navigation/stack- adds components that allow us to build stack-based navigation
-
react-native-gesture-handler- provides native-driven gesture management APIs for building the best possible touch-based experiences in React Native
Creating Basic Screens and Navigation
Let's dive into a code example:
// App.tsx
import { NavigationContainer, NavigationProp } from '@react-navigation/native';
import { StackScreenProps, createStackNavigator } from '@react-navigation/stack';
import { Text, View } from 'react-native';
export type StackNavigatorProps = {
Home: undefined;
UsersList: undefined;
UserProfile: { userId: number };
};
type NavigationType = NavigationProp<StackNavigatorProps>;
const HomeScreen: React.FC = () => {
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
}}>
<Text>Home Screen</Text>
</View>
);
};
// Users List Screen
const UsersListScreen: React.FC<StackScreenProps<StackNavigatorProps, 'UsersList'>> = ({ navigation }) => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Users List Screen</Text>
</View>
);
};
// User Profile Screen
const UserProfileScreen: React.FC<StackScreenProps<StackNavigatorProps, 'UserProfile'>> = ({ navigation, route }) => {
const { userId } = route.params;
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
}}>
<Text>User Profile ID: {userId}</Text>
</View>
);
};
const Stack = createStackNavigator<StackNavigatorProps>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="UsersList" component={UsersListScreen} />
<Stack.Screen name="UserProfile" component={UserProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
In this example, we have our screens composed. But after you copy and paste it into your app, then you will see one screen with the "Home Screen" text. At this moment, there is no navigation; you can't move to other screens :(
The highlighted fragment of the code gives us information about what this screen is and passes some props to it.
The navigation prop does the same thing as the useNavigation hook from my previous article about navigation. We can use it to move between screens etc.
A new thing is the route prop. This is a prop that contains various information regarding the current route. From it, we can get our navigation params for the current route. These navigation params are the same thing as navigation or search params on the WEB.
Moving between screens
If we want to move between screens, we can do it in two ways. The first one is by using the useNavigation hook, and we will do it on the Home screen. The second method is by using the navigation prop, and this will be used in the Users List and User Profile screens. Let's take a look:
// App.tsx
import { NavigationContainer, NavigationProp, useNavigation } from '@react-navigation/native';
import { StackScreenProps, createStackNavigator } from '@react-navigation/stack';
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
export type StackNavigatorProps = {
Home: undefined;
UsersList: undefined;
UserProfile: { userId: number };
};
type NavigationType = NavigationProp<StackNavigatorProps>;
// Home screen
const HomeScreen: React.FC = () => {
const navigation = useNavigation<NavigationType>();
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
}}>
<Text>Home Screen</Text>
<Pressable onPress={() => navigation.navigate('UsersList')} style={styles.button}>
<Text style={{ color: 'white' }}>Go to Users List Screen</Text>
</Pressable>
</View>
);
};
// Array with 100 elements for our users list
const usersList = Array.from({ length: 100 }, (_, index) => index + 1);
// Users List Screen
const UsersListScreen: React.FC<StackScreenProps<StackNavigatorProps, 'UsersList'>> = ({ navigation, route }) => {
return (
<View style={{ flex: 1 }}>
<FlatList
style={{ flex: 1 }}
data={usersList}
renderItem={({ item: userId }) => (
<Pressable
onPress={() => navigation.navigate('UserProfile', { userId })}
key={userId}
style={styles.userListItem}>
<Text>UserId: {userId}</Text>
</Pressable>
)}
/>
</View>
);
};
// User Profile Screen
const UserProfileScreen: React.FC<StackScreenProps<StackNavigatorProps, 'UserProfile'>> = ({ navigation, route }) => {
const { userId } = route.params;
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
}}>
<Text>User Profile ID: {userId}</Text>
<Pressable onPress={() => navigation.goBack()} style={styles.button}>
<Text style={{ color: 'white' }}>Go Back</Text>
</Pressable>
</View>
);
};
const Stack = createStackNavigator<StackNavigatorProps>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="UsersList" component={UsersListScreen} />
<Stack.Screen name="UserProfile" component={UserProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
button: {
borderRadius: 5,
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: 'blue',
},
userListItem: {
flex: 1,
margin: 8,
padding: 10,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderRadius: 10,
borderColor: 'grey',
},
});
So far so good, all screens are working correctly, but what just happened?
At line:
- 16 - Added a hook that manages our navigation.
- 27-29 - Added the navigation button to navigate to the next screen.
- 35 - Declared an array that will mock our users' data.
- 41-52 - Rendered the user list; we also added an item that we can press to go to the user profile.
- 73-75 - Added a navigation button that allows us to go back to the previous screen.
Additionally, as you've probably noticed, there's a top bar at the top of the screen displaying the current screen's name. When navigating to the next screen, a "go back" button will appear. This allows the user to return to the previous screen in cases where there's no additional logic for navigation on the last screen. We can also choose to hide this top bar; an example is provided below:
const Stack = createStackNavigator<StackNavigatorProps>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} options={{ headerShown: false }} />
<Stack.Screen name="UsersList" component={UsersListScreen} />
<Stack.Screen name="UserProfile" component={UserProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
All screens have an additional options prop that allows us to customize them slightly. You can pass an object with configuration to it. In this case, the prop that allows us to hide the top bar is headerShow. We can pass only a boolean value to it.
Conclusion
We've explored Stack Navigation in React Native, detailing how to set it up and navigate between screens using React Navigation. This article has shown the flexibility and customization Stack Navigation offers, from managing screen transitions to navigating without a visible bar. It lays the groundwork for further enhancing React Native apps with advanced navigation patterns, promising more insights in future articles.