Tab Bar

The TabBar component gives you a native tabbed interface in iOS and Android. Each tab has its own stack of screens. To ensure that pushing and popping in one tab doesn't affect the stack in another tab, you give each tab its own StateNavigator. Let's add tabs to our email example. One tab for the user's inbox and another for their list of contacts. We create a new StateNavigator for the 'contacts' tab. It contains two States so the user can view all their contacts and edit the details of an individual contact.

var contactsNavigator = new StateNavigator([
  {key: 'contacts'},
  {key: 'contact', trackCrumbTrail: true}
]);

var {contacts, contact} = stateNavigator.states;
contacts.renderScene = () => <Contacts />;
contact.renderScene = ({id}) => <Contact id={id} />;

stateNavigator.navigate('contacts');

You create a NavigationStack for each tab and give each one a dedicated StateNavigator. Set the primary prop to 'false' on all NavigationStacks except the first. This ensures that pressing the Android back button on the first scene of tab two goes to tab one instead of closing the app. The App only closes when pressing the Android back on the first scene of the primary stack.

Wrap each stack in a TabBarItem component and add them as children of a TabBar. You identify your tabs using the title and image props of the TabBarItem. On iOS, you must render the TabBar component at the root of your app. On Android, you can render the TabBar anywhere (including inside another TabBar). In our email example, we set the systemItem prop so that the 'contacts' tab displays with the inbuilt iOS icon for contacts.

import {TabBar, TabBarItem} from 'navigation-react-native';

<TabBar>
  <TabBarItem title="Inbox">
    <NavigationHandler stateNavigator={stateNavigator}>
      <NavigationStack />
    </NavigationHandler>
  </TabBarItem>
  <TabBarItem title="Contacts" systemItem="contacts">
    <NavigationHandler stateNavigator={contactsNavigator}>
      <NavigationStack primary={false} />
    </NavigationHandler>
  </TabBarItem>
</TabBar>

Adding a Bottom Tab Bar on Android

In a standard Android app, the tabs only appear on the first scene. When the user navigates to a new scene the tabs disappear. You can replicate this setup using the same TabBar component as above. But you don't need a separate NavigationStack per tab anymore. In their place you put the components that make up the tab content.

In our email example, we create a 'home' scene that renders the TabBar with two tabs. In the first tab we place our Inbox component and we put the Contacts component into the second tab. We set the bottomTabs prop of the TabBar to true because, in the usual Android setup, the tabs appear at the bottom of the screen.

var Home =  () => (
  <TabBar bottomTabs={true} swipeable={false}>
    <TabBarItem title="Inbox">
      <Inbox />
    </TabBarItem>
    <TabBarItem title="Contacts">
      <Contacts />
    </TabBarItem>
  </TabBar>
);

We can make do with a single StateNavigator now that we no longer have separate NavigationStacks. We can also get rid of the 'inbox' State from the StateNavigator because it's not a scene in its own right anymore. The Inbox component is now part of the 'home' scene. Instead, we need to add a 'home' State and make sure that it's displayed when the app first loads.

var stateNavigator = new StateNavigator([
  {key: 'home'},
  {key: 'mail', trackCrumbTrail: true},
  {key: 'compose', trackCrumbTrail: true},
  {key: 'contact', trackCrumbTrail: true}
]);

var {home} = stateNavigator.states;
home.renderScene = () => <Home />;

stateNavigator.navigate('home');

Scrolling the Navigation Bar on Android

In our email example on Android, we separate the personal contacts from the work ones by having a separate tab for each. We scroll the navigation bar off the screen so that these inner tabs don't look squashed. After wrapping the tabs in a CoordinatorLayout and turning on nested scrolling, we must add an extra TabBar component into the navigation bar. It doesn't need any TabBarItem children because its job is to keep the tab headings in place as the navigation bar scrolls.

var Contacts = () => (
  <CoordinatorLayout>
    <NavigationBar title="Contacts">
      <TabBar />
    </NavigationBar>
    <TabBar>
      <TabBarItem title="Personal">
        <ScrollView nestedScrollEnabled={true} />
      </TabBarItem>
      <TabBarItem title="Work">
        <ScrollView nestedScrollEnabled={true} />
      </TabBarItem>
    </TabBar>
  </CoordinatorLayout>
);