by

React Native has a core component called TabBarIOS, which renders a tab bar at the bottom of the screen on iOS platform. But it doesn’t work on Android. If you want to have tabs in your app that look and work the same on both, iOS and Android, follow this tutorial, and in 15 minutes you’ll learn how to build them.

If you don’t have any experience with React Native check out 3 Steps to Build Your First Mobile App with React Native. It explains the basics and how to set up required software to get started.

What We Will Be Building

We’re going to build an app that has a tab bar with three tabs at the top. And each tab has some content. That’s how it’s going to look like.

For your reference, the final code for the app we’re building can be found in this GitHub repo.

What Do You Need To Know

Here is a list of all different things we’re going to use to build the app. It includes various React Native core components, other features, such as state or props, and various JavaScript methods or expressions, such as .map() or object destructuring.

  • View. View is a fundamental container component for building app’s UI. It can be nested inside other views and have children of any type. Read more
  • Text. Text is a component for displaying text. Read more
  • StyleSheet. With React Native you can style your components with CSS-like styles. Style names and values usually match CSS styles, except names are written like backgroundColor instead of like background-color. Read more
  • Custom Components. You’re not limited to using React Native core components. You can create you own. Read more
  • Props. Props are used to customize React Native components, both core, and your own components. You can use the same component in different parts of your app with different props to customize how it would look or behave. Read more
  • State. Components in React Native use state for data that changes over time. Most of your components should not use state, but take some data from props and render it. However, sometimes you need to respond to user input. Read more
  • TouchableOpacity. A wrapper component that allows views to respond to touches. Read more
  • Javascript .map() Iterator. The map() method creates a new array with the results of calling a provided function on every element in this array. Read more
  • JavaScript ES6 Object Destructuring. We’ll use this ES6 feature to pull fields from objects passed as function parameter. Read more
  • JavaScript ES6 Arrow Functions. An arrow function expression (also known as fat arrow function) has a shorter syntax compared to function expressions and lexically binds the this value. Arrow functions are always anonymous. For example a shorter version of function times2(value) { return value * 2; }, using fat arrow function would be value => value * 2 . Read more

You can always get back to this list as you go if you find yourself having a hard time understanding how some of these things work.

Initialize New Project

Let’s start off by creating a new app. Open Terminal App and run these commands to initialize a new project and run it in an emulator.

react-native init Tabs;
cd Tabs;
react-native run-ios;

Enable Hot Reloading

Once your app is up and running, press D and select Enable Hot Reloading. This will save you some time having to reload the app manually every time you make a change.

Index Files

We’re going to re-use the same code for both, iOS and Android, so we don’t need two different index files. We’re going to create a file called app.js to store our code and import that file in index.ios.js and index.android.js files.

Open index.ios.js file and scrap all of the React Native boilerplate code to start from scratch. Do the same for index.android.js. And add to both of them the following code.

import { AppRegistry } from 'react-native';
import App from './app';

AppRegistry.registerComponent('Tabs', () => App);

This code imports App component from app.js file and registers it as main app container. If you took at look at the emulator at this point, you would see an error screen. That’s because app.js doesn’t exist yet, and therefore can’t be imported. So, let’s fix it.

App Component

Let’s start off by creating just a single screen with no tabs first. Create a new file and call it app.js.

Import Components

First, import all component we’re going to use.

import React, { Component } from 'react';
import {
  StyleSheet,   // CSS-like styles
  Text,         // Renders text
  View          // Container component
} from 'react-native';

Define App Class

Next, define App component class, which has required render() method that returns a View with a header and some text.

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.content}>
          <Text style={styles.header}>
            Welcome to React Native
          </Text>
          <Text style={styles.text}>
            The best technology to build cross platform mobile apps with
          </Text>
        </View>
      </View>
    );
  }
}

Add Styles

And lastly, add styles to change text size, color, background color, center it all on the screen, and stuff like that.

const styles = StyleSheet.create({
  // App container
  container: {
    flex: 1,                            // Take up all screen
    backgroundColor: '#E91E63',         // Background color
  },
  // Tab content container
  content: {
    flex: 1,                            // Take up all available space
    justifyContent: 'center',           // Center vertically
    alignItems: 'center',               // Center horizontally
    backgroundColor: '#C2185B',         // Darker background for content area
  },
  // Content header
  header: {
    margin: 10,                         // Add margin
    color: '#FFFFFF',                   // White color
    fontFamily: 'Avenir',               // Change font family
    fontSize: 26,                       // Bigger font size
  },
  // Content text
  text: {
    marginHorizontal: 20,               // Add horizontal margin
    color: 'rgba(255, 255, 255, 0.75)', // Semi-transparent text
    textAlign: 'center',                // Center
    fontFamily: 'Avenir',
    fontSize: 18,
  },
});

Check Our Progress

Bring up the emulator window and see what we’ve got so far. Looks pretty good so far, but there is no tabs yet. Let’s deal with this next.

Adding Tabs

Fist, add the import statement for Tabs component that we’re about to create.

import Tabs from './tabs';

And change render() method to have multiple screens for different tabs, and wrap them into Tabs component.

  render() {
    return (
      <View style={styles.container}>
        <Tabs>
          {/* First tab */}
          <View title="WELCOME" style={styles.content}>
            <Text style={styles.header}>
              Welcome to React Native
            </Text>
            <Text style={styles.text}>
              The best technology to build cross platform mobile apps with
            </Text>
          </View>
          {/* Second tab */}
          <View title="NATIVE" style={styles.content}>
            <Text style={styles.header}>
              Truly Native
            </Text>
            <Text style={styles.text}>
              Components you define will end up rendering as native platform widgets
            </Text>
          </View>
          {/* Third tab */}
          <View title="EASY" style={styles.content}>
            <Text style={styles.header}>
              Ease of Learning
            </Text>
            <Text style={styles.text}>
              It’s much easier to read and write comparing to native platform’s code
            </Text>
          </View>

        </Tabs>
      </View>
    );
  }

We’ve just added two more Views, title prop to each of three, and wrapped them into Tabs component. The title prop is not a prop supported by View component itself. We’re going to read this prop value in Tabs component and use to display a tab title on a tab bar.

Tabs Component

And finally, let’s create Tabs component. As you noticed it’s going to receive multiple View components with title prop for tab title, and tab content inside it.

Create a new file and call it tabs.js.

Import Components

First, import all component we’re going to use.

import React, { Component } from 'react';
import {
  StyleSheet,         // CSS-like styles
  Text,               // Renders text
  TouchableOpacity,   // Pressable container
  View                // Container component
} from 'react-native';

Define Tabs Class

Next, define Tabs component class.

export default class Tabs extends Component {

  // Initialize State
  state = {
    // First tab is active by default
    activeTab: 0
  }

  // Pull children out of props passed from App component
  render({ children } = this.props) {
    return (
      <View style={styles.container}>
        {/* Tabs row */}
        <View style={styles.tabsContainer}>
          {/* Pull props out of children, and pull title out of props */}
          {children.map(({ props: { title } }, index) =>
            <TouchableOpacity
              style={[
                // Default style for every tab
                styles.tabContainer,
                // Merge default style with styles.tabContainerActive for active tab
                index === this.state.activeTab ? styles.tabContainerActive : []
              ]}
              // Change active tab
              onPress={() => this.setState({ activeTab: index }) }
              // Required key prop for components generated returned by map iterator
              key={index}
            >
              <Text style={styles.tabText}>
                {title}
              </Text>
            </TouchableOpacity>
          )}
        </View>
        {/* Content */}
        <View style={styles.contentContainer}>
          {children[this.state.activeTab]}
        </View>
      </View>
    );
  }
}

It has state with activeTab value, which is 0 by default, what means that first tab is active when you launch the app. You might be wondering why is it 0 and not 1. That’s because javascript array element indexes start with 0, so the first element always has index 0, the second one has index 1, and so on.

In render() it loops through children prop, which is an array of those three Views that we wrapped into Tabs component in app.js, and returns tappable TouchableOpacity component for each tab. When it’s tapped it updates activeTab in state.

And then it renders active tab’s content from children array, using this.state.activeTab index.

Add Styles

And lastly, add styles to style how tabs look.

const styles = StyleSheet.create({
  // Component container
  container: {
    flex: 1,                            // Take up all available space
  },
  // Tabs row container
  tabsContainer: {
    flexDirection: 'row',               // Arrange tabs in a row
    paddingTop: 30,                     // Top padding
  },
  // Individual tab container
  tabContainer: {
    flex: 1,                            // Take up equal amount of space for each tab
    paddingVertical: 15,                // Vertical padding
    borderBottomWidth: 3,               // Add thick border at the bottom
    borderBottomColor: 'transparent',   // Transparent border for inactive tabs
  },
  // Active tab container
  tabContainerActive: {
    borderBottomColor: '#FFFFFF',       // White bottom border for active tabs
  },
  // Tab text
  tabText: {
    color: '#FFFFFF',
    fontFamily: 'Avenir',
    fontWeight: 'bold',
    textAlign: 'center',
  },
  // Content container
  contentContainer: {
    flex: 1                             // Take up all available space
  }
});

And There It Is

Bring up the emulator window and play around with the app. Try tapping tabs and see how the app works.

Takeaways

You’ve just built a component that you can re-use in your apps for both, iOS and Android platforms. This way your apps will look consistent on both platforms, and you won’t have to worry about building and maintaining two different tab components for each platform.

Spread the Word
  • great tutorial ,, thanks mate !

  • Billy Sutomo

    Nice tutorial !!
    easy to understand, thanks!

  • Nicholas Eduardo

    Thanks a lot

  • youyou

    Great tutorial, thanks! Question: how would you modify the code to have the tab bar at the bottom instead? I had no luck.

    • You can just switch tabsContainer and contentContainer views, so contentContainer comes first and tabsContainer after. This way tabs are at the bottom.

  • Henry Jose Romero Parra

    Hi, excellent option, I have a quetion: How to chane title color?

  • winwinsituation

    How do you manage the state of the tabs when switching?

  • Hashini Weerasekara

    how to add an icon instead of the text in the tab title
    ?