Sitemap

React Native Stack Navigation with Tab Navigation

5 min readDec 14, 2023

--

Photo by Denny Müller on Unsplash

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.

Graph 1. Flow diagram of the initial sequence.

The project and dependencies

Let’s create a bare React Native project with expo by running:

npx create-expo-app AppName --template bare-minimum

Install 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-tabs

The 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 Page
import { 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 HomeNavigation

Now, 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 LoginScreen
import { 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 OnBoarding

Up to this point, our setup includes:

  • Each Page features a logout button that redirects users to the Login screen.
  • HomeNavigation acts as a Tab.Navigator holding three distinct screens (Page).
  • The OnBoarding screen has a button to reset the navigation tree, guiding users back to the Home screen.
  • In the Login screen, a simulated sign-in button is in place, triggering a navigation tree reset that leads users back to the Home screen 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 AppNavigation

As 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 App

Thanks 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.

--

--

Cristhian Leon
Cristhian Leon

Written by Cristhian Leon

Professional iOS Developer with a passion for building performant, elegant apps — Golang enthusiast.

No responses yet