Using React Navigation 5 with UI Kitten

Artur Yorsh

Artur Yorsh

UI Kitten Team

This is a guest post by the UI Kitten team. If you like this guide, checkout UI Kitten for more! In this blog post, we'll show a step-by-step guide on using React Navigation 5 with UI Kitten.

Introduction

The new React Navigation comes with several significant improvements such as improving animation performance with gesture-handler and reanimated libraries. What's more, it was migrated to TypeScript for improving the quality of your code base with type checking and more. But the biggest update is migrating to component-based API.

Eva Design System is a customizable Design System that is easy to adapt to your brand. It provides Mobile and Web component libraries and allows businesses to quickly create beautiful unique branding themes. The React Native realization of Eva Design System includes UI Kitten, React Native framework for building modern cross-platform mobile applications.

The UI Kitten team started actively using React Navigation alpha and we're proud to announce the full compatibility to the new React Navigation API. In this guide, we won't consider how to implement all of the boilerplate stuff like auth screens. Instead, we will learn how to navigate between screens using Drawer, Bottom Tabs, Top Tabs, and Stack navigators to build a TODO-App. Furthermore, we'll demonstrate using React Navigation with UI Kitten components.

Overview

React Navigation 5 is nothing else rather than simplifying navigation structure in your app.

import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
export const AuthNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='Sign In' component={SignInScreen}/>
<Stack.Screen name='Sign Up' component={SignUpScreen}/>
</Stack.Navigator>
);

To create a navigator, you import createXNavigator function from the navigator package of your choice and use Navigator and Screen components from the value it returns.

Unlike the previous React Navigation versions, all the screens used inside a navigator are passed as child elements with wrapping it to a Screen component. If you need to set up additional navigator configuration like configuration of the header, you can simply pass corresponding props directly to the Navigator component.

Getting started

Clone the project from GitHub. It contains all the required source code for the initial setup.

git clone https://github.com/artyorsh/react-navigation-ex-demo

Step 1. Authentication flow

Assuming that your app users will need to authorize before getting to the home screen, we will need to create both Authentication and Home navigators. Then we're going to combine it with simple stack navigation and pick the initial screen depending on the user authorization status.

Open ./src/navigation/auth.navigator.tsx` file and paste the following code:

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AppRoute } from './app-routes';
import { SignInScreen, SignUpScreen, ResetPasswordScreen } from '../scenes/auth';
const Stack = createStackNavigator();
export const AuthNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.SIGN_IN} component={SignInScreen}/>
<Stack.Screen name={AppRoute.SIGN_UP} component={SignUpScreen}/>
<Stack.Screen name={AppRoute.RESET_PASSWORD} component={ResetPasswordScreen}/>
</Stack.Navigator>
);

In this example, we're using a createStackNavigator function to create simple stack navigation between Sign In, Sign Up and Reset Password screens. Under Stack Navigator we mean the default navigation behavior between screens: with slide-from-right animation on iOS, and slide-in-top on Android.

In ./src/navigation/app.navigator.tsx file and replace the placeholder screen with Auth Navigator. This will make authentication screens to be the starter point of your app.

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { AppRoute } from './app-routes';
const Stack = createStackNavigator();
export const AppNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
</Stack.Navigator>
);

Step 2. Top tabs

Say, our app has both in-progress and finished tasks. So, you should separate them to avoid a mess. Here you can make it with two tabs on the home screen. To do this, we need to have three screens: two for tabs and one master screen for navigation management between tabs. Unlike the Stack Navigator component, the Top Tabs Navigator has a special prop for the component to control navigation between tabs - tabBar. We will use it to configure the tab bar with UI Kitten components.

Open ./src/navigation/todo.navigator.tsx` file and paste the following code:

import React from 'react';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { AppRoute } from './app-routes';
import { TodoTabBar, TodoInProgressScreen, TodoDoneScreen } from '../scenes/todo';
import { DoneAllIcon, GridIcon } from '../assets/icons';
const TopTab = createMaterialTopTabNavigator();
export const TodoNavigator = (): React.ReactElement => (
<TopTab.Navigator tabBar={props => <TodoTabBar {...props} />}>
<TopTab.Screen
name={AppRoute.TODO_IN_PROGRESS}
component={TodoInProgressScreen}
options={{ title: 'IN PROGRESS', tabBarIcon: GridIcon }}
/>
<TopTab.Screen
name={AppRoute.TODO_DONE}
component={TodoDoneScreen}
options={{ title: 'DONE', tabBarIcon: DoneAllIcon }}
/>
</TopTab.Navigator>
);

The code above will enable you to navigate with gestures between In Progress screen and Done screen, but not set up the Tab Bar. Open ./src/scenes/todo/todo-tab-bar.component.tsx file and paste the following code:

import React from 'react';
import { TabBar, Tab, Divider, TabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset, SafeAreaLayoutElement } from '../../components/safe-area-layout.component';
import { Toolbar } from '../../components/toolbar.component';
export const TodoTabBar = (props): SafeAreaLayoutElement => {
const onTabSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};
const createNavigationTabForRoute = (route): TabElement => {
const { options } = props.descriptors[route.key];
return (
<Tab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};
return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Toolbar title='React Navigation Ex 🐱'/>
<TabBar selectedIndex={props.state.index} onSelect={onTabSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</TabBar>
<Divider/>
</SafeAreaLayout>
);
};

With the code above we render TabBar component with two tabs inside: one per each screen inside TodoNavigator. Then, we use the React Navigation state to pass selectedIndex and onSelect props to navigate between screens. So, when the user taps one of the tabs, the TabBar component calls onTabSelect function and this is the place where we need to navigate to the corresponding route.

Finally, open app.navigator.tsx file and add the TodoNavigator as a Home screen. Now you're able to navigate the home screen with todo tabs after sign in.

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { TodoNavigator } from './todo.navigator';
import { AppRoute } from './app-routes';
const Stack = createStackNavigator();
export const AppNavigator = (props): React.ReactElement => (
<Stack.Navigator {...props} headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
<Stack.Screen name={AppRoute.HOME} component={TodoNavigator}/>
</Stack.Navigator>
);

Step 3. Bottom tabs

Sometimes you may want your app to contain tabs at the bottom. Here is the main semantic difference regarding the tabs at the top: while they should represent the content of the same type, the bottom tabs could be used to show any content of your application. This is where we're going to use createBottomTabNavigator and BottomNavigation.

Let's start by creating another navigator for the second tab. The first one will be used for Todo screens. Open ./src/navigation/profile.navigator.tsx file and paste the following code: 

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AppRoute } from './app-routes';
import { ProfileScreen } from '../scenes/profile';
const Stack = createStackNavigator();
export const ProfileNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.PROFILE} component={ProfileScreen}/>
</Stack.Navigator>
);

This will add a simple stack navigator, just like we did it for authentication flow.

Now we need to somehow connect TodoNavigator with ProfileNavigator . The implementation is as simple as creating a navigator for top tabs. Thanks to React Navigation, we have totally the same API for this. Open ./src/navigation/home.navigator.tsx file and paste the following code:

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { TodoNavigator } from './todo.navigator';
import { ProfileNavigator } from './profile.navigator';
import { AppRoute } from './app-routes';
import { HomeTabBar } from '../scenes/home';
import { LayoutIcon, PersonIcon } from '../assets/icons';
const BottomTab = createBottomTabNavigator();
export const HomeNavigator = (): React.ReactElement => (
<BottomTab.Navigator tabBar={props => <HomeTabBar {...props} />}>
<BottomTab.Screen
name={AppRoute.TODO}
component={TodoNavigator}
options={{ title: 'TODO', tabBarIcon: LayoutIcon }}
/>
<BottomTab.Screen
name={AppRoute.PROFILE}
component={ProfileNavigator}
options={{ title: 'PROFILE', tabBarIcon: PersonIcon }}
/>
</BottomTab.Navigator>
);

Just like in the case with tabs at the top, we also need to make a custom tabBar. Open ./src/scenes/home/home-tab-bar.component.tsx file and paste the following code:

import React from 'react';
import { BottomNavigation, BottomNavigationTab, Divider, BottomNavigationTabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SafeAreaLayoutElement, SaveAreaInset } from '../../components/safe-area-layout.component';
export const HomeTabBar = (props): SafeAreaLayoutElement => {
const onSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};
const createNavigationTabForRoute = (route): BottomNavigationTabElement => {
const { options } = props.descriptors[route.key];
return (
<BottomNavigationTab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};
return (
<SafeAreaLayout insets={SaveAreaInset.BOTTOM}>
<Divider/>
<BottomNavigation
appearance='noIndicator'
selectedIndex={props.state.index}
onSelect={onSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</BottomNavigation>
</SafeAreaLayout>
);
};

Using the code above we render BottomNavigation component with two tabs inside: one per each screen inside HomeNavigator. We use the React Navigation state to pass selectedIndex and onSelect props to navigate between screens. So, when the user taps one of the tabs, the BottomNavigation component calls onSelect function. Well, this is the place where we need to navigate to the corresponding route.

Then, open app.navigator.tsx file and replace the TodoNavigator with HomeNavigator:

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { HomeNavigator } from './home.navigator';
import { AppRoute } from './app-routes';
const Stack = createStackNavigator();
export const AppNavigator = (props): React.ReactElement => (
<Stack.Navigator {...props} headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
<Stack.Screen name={AppRoute.HOME} component={HomeNavigator}/>
</Stack.Navigator>
);

Step 4. Drawer menu

At the final stage of this guide, we will describe how to create the drawer navigation. While the top and bottom tabs can be used to present the main product features, a drawer menu can be also used to direct a user to legal information about it, or simply contain quick actions like a logout.

Usually, the drawer menu is available in app on the home screen, so let's add it to HomeNavigator. Open ./src/navigation/home.navigator.tsx file and paste the following code:

import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { TodoNavigator } from './todo.navigator';
import { ProfileNavigator } from './profile.navigator';
import { AppRoute } from './app-routes';
import { HomeTabBar, HomeDrawer, AboutScreen } from '../scenes/home';
import { HomeIcon, InfoIcon, LayoutIcon, PersonIcon } from '../assets/icons';
const Drawer = createDrawerNavigator();
const BottomTab = createBottomTabNavigator();
const HomeBottomNavigator = (): React.ReactElement => (
<BottomTab.Navigator tabBar={props => <HomeTabBar {...props} />}>
<BottomTab.Screen
name={AppRoute.TODO}
component={TodoNavigator}
options={{ title: 'TODO', tabBarIcon: LayoutIcon }}
/>
<BottomTab.Screen
name={AppRoute.PROFILE}
component={ProfileNavigator}
options={{ title: 'PROFILE', tabBarIcon: PersonIcon }}
/>
</BottomTab.Navigator>
);
export const HomeNavigator = (): React.ReactElement => (
<Drawer.Navigator drawerContent={props => <HomeDrawer {...props} />}>
<Drawer.Screen
name={AppRoute.HOME}
component={HomeBottomNavigator}
options={{ title: 'Home', drawerIcon: HomeIcon }}
/>
<Drawer.Screen
name={AppRoute.ABOUT}
component={AboutScreen}
options={{ title: 'About', drawerIcon: InfoIcon }}
/>
</Drawer.Navigator>
);

In this example, we've implemented a Drawer Navigator with createDrawerNavigator and used it to display on the Home screen. We have also added AboutScreen to demonstrate navigation directly from the Drawer menu.

Just like Top/Bottom tab navigators, the drawer navigator also has a special property for declaring custom drawer view. Use a drawerContent property to pass the custom view to the navigator. Open ./src/scenes/home/home-drawer.component.tsx file and add the following code:

import React from 'react';
import { Drawer, DrawerItem, DrawerElement DrawerItemElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset } from '../../components/safe-area-layout.component';
export const HomeDrawer = (props): DrawerElement => {
const onItemSelect = (index: IndexPath): void => {
const selectedTabRoute: string = props.state.routeNames[index.row];
props.navigation.navigate(selectedTabRoute);
props.navigation.closeDrawer();
};
const createDrawerItemForRoute = (route, index: number): DrawerItemElement => {
const { options } = props.descriptors[route.key];
return (
<DrawerItem
key={index}
title={route.name}
accessoryLeft={options.drawerIcon}
/>
);
};
return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Drawer
data={props.state.routes.map(createNavigationItemForRoute)}
onSelect={onMenuItemSelect}
/>
</SafeAreaLayout>
);
};

Due to the use of this code, we render Drawer component with two actions inside: one for navigating to legal information screen and one for performing a user logout. Then, we pass data prop to display our actions and onSelect prop to handle it. So, when the user taps the action, the Drawer component calls onMenuItemSelect function and this is the place where we need to handle it.`

The next thing to do is to modify the Todo tab bar by adding a menu icon to open a drawer. Open ./src/scenes/todo/todo-tab-bar.component.tsx file and paste the following code:

import React from 'react';
import { TabBar, Tab, Divider, TabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset, SafeAreaLayoutElement } from '../../components/safe-area-layout.component';
import { Toolbar } from '../../components/toolbar.component';
import { MenuIcon } from '../../assets/icons';
export const TodoTabBar = (props): SafeAreaLayoutElement => {
const onTabSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};
const createNavigationTabForRoute = (route): TabElement => {
const { options } = props.descriptors[route.key];
return (
<Tab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};
return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Toolbar
title='React Navigation Ex 🐱'
backIcon={MenuIcon}
onBackPress={props.navigation.toggleDrawer}
/>
<TabBar selectedIndex={props.state.index} onSelect={onTabSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</TabBar>
<Divider/>
</SafeAreaLayout>
);
};

TypeScript

The new React Navigation has great TypeScript support and exports type definitions for navigators and custom navigation components. Sometimes you may want to type-check the params you're passing when navigating between routes. You also may want to make autocomplete work when working with navigation props. 

Let's add some type definitions for Auth screens. For this purpose, open ./src/navigation/auth.navigator.tsx and paste the following code:

import { RouteProp } from '@react-navigation/core';
import { StackNavigationProp } from '@react-navigation/stack';
import { AppRoute } from './app-routes';
type AuthNavigatorParams = {
[AppRoute.SIGN_IN]: undefined;
[AppRoute.SIGN_UP]: undefined;
[AppRoute.RESET_PASSWORD]: undefined;
}
export interface SignInScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.SIGN_IN>;
route: RouteProp<AuthNavigatorParams, AppRoute.SIGN_IN>;
}
export interface SignUpScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.SIGN_UP>;
route: RouteProp<AuthNavigatorParams, AppRoute.SIGN_UP>;
}
export interface ResetPasswordScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.RESET_PASSWORD>;
route: RouteProp<AuthNavigatorParams, AppRoute.RESET_PASSWORD>;
}

Now you can modify props of Auth screens props by adding types to make your autocomplete and IntelliSense work. For more complex examples, consider reading type-checking doc or reviewing complete demo application sources.

Useful links

By the links below, you can find a lot of useful information about UI Kitten and React Navigation 5. The demo application may contain more complex examples. Also, by referring to the app built by the React Navigation team, you can find plenty of useful examples too.