React Native Stack Navigation with Tab Navigation
Navigating between screens is a fundamental aspect of mobile app development, and in the realm of React Native, mastering navigation is key to creating a seamless user experience. In this article, we delve into the powerful combination of Stack Navigation and Tab Navigation in React Native.
We’ll construct a basic user flow for a typical authentication-based mobile app. Together, we’ll build a common initial sequence that integrates checking if the user is logged in, if it’s the first time, and redirect the user to the proper screen.
The project and dependencies
Let’s create a bare React Native project with expo by running:
npx create-expo-app AppName --template bare-minimumInstall react navigation libraries. For more information, please visit the official documentation.
# Install the core library
npm install @react-navigation/native
# Install the dependencies
npm install react-native-screens react-native-safe-area-context
# Install Navigation Stack
npm install @react-navigation/native-stack
# Install Bottom Tabs
npm install @react-navigation/bottom-tabsThe idea
We’ll implement the flow outlined in Graph 1 using a structured approach. Our AppNavigation serves as a StackNavigator with three main children: Login, Onboarding, and Home. Note that, Home also functions as a TabNavigator with three additional children: Page 1, Page 2, and Page 3. This hierarchical setup provides a clear and organized navigation structure for our application.
The solution
Then, let’s create the lower components, Home Tab, and its three screens: Page 1, Page 2, and Page 3.
// This is ReactNative hook to handle navigation
// you can also receive it as a prop. I prefer this one.
import { useNavigation } from "@react-navigation/native"
import { Text, Button, View } from "react-native"
const Page = () => {
const navigation = useNavigation()
return <View>
<Text>Page</Text>
<Button
title="Log out"
// navigation reset allows us to reset the navigation hierarchy
// and set 'Login' as the root.
onPress={() => navigation.reset({ index: 0, routes: [{ name: 'Login' }] })}
/>
</View>
}
export default Pageimport { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
// Import this from wherever your pages are located
import Page from '@screens/Page'
const HomeNavigation = () => {
// Attention, this is a BottomTab
const Tab = createBottomTabNavigator()
return (
<Tab.Navigator
initialRouteName='Page1'
screenOptions={{ headerShown: false }}
>
<Tab.Screen name='Page1' component={Page} />
<Tab.Screen name='Page2' component={Page} />
<Tab.Screen name='Page3' component={Page} />
</Tab.Navigator>
)
}
export default HomeNavigationNow, the Login, and OnBoarding screens.
import { useNavigation } from "@react-navigation/native"
import { View, Text, Button } from "react-native"
const LoginScreen = () => {
const navigation = useNavigation()
return <View>
<Text>Landing</Text>
<Button
// reset the navigation tree, to prevent users to go back
onPress={() => navigation.reset({ index: 0, routes: [{ name: 'Home' }] })}
title='Sign In'
/>
</View>
}
export default LoginScreenimport { useNavigation } from "@react-navigation/native"
import { View, Text, Button } from "react-native"
const OnBoarding = () => {
const navigation = useNavigation()
return <View>
<Text>Welcome</Text>
<Button
onPress={() => navigation.reset({ index: 0, routes: [{ name: 'Home' }] })}
title='Go Home'
/>
</View>
}
export default OnBoardingUp to this point, our setup includes:
- Each
Pagefeatures a logout button that redirects users to theLoginscreen. HomeNavigationacts as aTab.Navigatorholding three distinct screens (Page).- The
OnBoardingscreen has a button to reset the navigation tree, guiding users back to theHomescreen. - In the
Loginscreen, a simulated sign-in button is in place, triggering a navigation tree reset that leads users back to theHomescreen as well.
Putting all together
Let’s plug the final piece, the AppNagivation Stack.Navigator . This one is going to hold the three screens on the second layer we previously created, Login, Home, and OnBoarding .
Source Codeimport { createNativeStackNavigator } from '@react-navigation/native-stack'
import { NavigationContainer } from '@react-navigation/native'
// Screens we previously created
import HomeNavigation from './HomeNavigation'
import LoginScreen from '@screens/LoginScreen'
import OnBoarding from '@screens/OnBoarding'
const AppNavigation = ({ initialRoute }) => {
// Attention, this is a Stack
const Stack = createNativeStackNavigator()
// Please refer to the Graph2, where all modules are connected
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRoute}>
<Stack.Screen name="OnBoarding" component={OnBoarding} />
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Home" component={HomeNavigation} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default AppNavigationAs you can see, the AppNavigation has a prop named initialRoute, which must be passed from the App.js component. Within App, we will conditionally render AppNavigation and the SplashScreen checking for isLoggedIn, and isFirstTime . From this point on, it will depend
on the specific requirements of your project. Feel free to adapt it to your needs.
import { useEffect, useState } from 'react';
import { View, Text } from 'react-native'
// The AppNavigation we previously created
import AppNavigation from '@navigation/AppNavigation';
// This view can match the native (iOS Launch | Android Splash) look.
// To redirect the user seemlessly from launch to the final route.
const SplashScreen = () => {
return <View
style={{ backgroundColor: 'tomato', flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text>Logo Here</Text>
</View>
}
function App() {
const [isLoading, setIsLoading] = useState(true)
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isFirstTime, setIsFirstTime] = useState(false)
// Fake fetching user, that takes 2 seconds, and just set the
// isLoading to false. The idea is to show the SplashScreen, and then,
// go to Home, OnBoarding, or Login.
useEffect(() => {
const timeout = setTimeout(() => {
setIsLoading(false)
}, 2000)
return () => { clearTimeout(timeout) }
}, [])
// Calculate what the initial route is going to be, depending
// on the user state, and first time usage. This can be read
// from anywhere, db, shared preferences, user defaults, storage, etc.
const initialRoute = () => {
if (isLoggedIn) return 'Home'
if (isFirstTime) return 'OnBoarding'
return 'Login'
}
if (isLoading) return <SplashScreen />
return <AppNavigation initialRoute={initialRoute} />
}
export default AppThanks for reading. I hope you have enjoyed this small tutorial, and if it was useful for you, don’t be shy to 👏 on this article. See you next time.
