leabum
leabum

Reputation: 395

Having two tab navigation bars in React Navigation

I want to create an app, that has both a fixed bottom and top tab navigation bar. See image: enter image description here

After I finished the bottom navigation bar I tried the following in my App.js file:

return(
    <NavigationContainer>
        <Tab.Navigator>    //top navbar
            <Tab.Screen />
            ...
        </Tab.Navigator>
        <Tab.Navigator>    //bottom navbar
            <Tab.Screen />
            ...
        </Tab.Navigator>
    </NavigationContainer>
)

However, I get the error, that another navigator is already registered in this container and that I should not have multiple navigators under a single NavigationContainer. I found multiple guides about nesting tab and stack navigators, but how do I nest multiple tab navigators, that both update the central screen?

Upvotes: 0

Views: 2492

Answers (1)

Marek Lisik
Marek Lisik

Reputation: 2185

AFAIK that is not possible without writing a custom navigator. Navigators need to be nested and need to have separate routes, so one tab navigator would need to be nested inside (as a tab of) the other.

Writing a custom navigator is something you definitely could consider. Here is a snack that modifies the example from react-navigation documentation:

https://snack.expo.io/@mlisik/thoughtful-stroopwafels

In the snack, the first two tabs are displayed on top, and remaining on the bottom. You would need to further modify them to match the appearance you are after with some custom options, perhaps reusing internal components from react-navigation.

It is by no means a complete solution, but should give you an idea of what is possible.

For completeness, I include the code here:

// App.js

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNavigator } from './Navigator';

const Nav = createNavigator()

const Screen1 = () => <View style={{flex: 1, backgroundColor: 'red'}} />
const Screen2 = () => <View style={{flex: 1, backgroundColor: 'green'}} />
const Screen3 = () => <View style={{flex: 1, backgroundColor: 'yellow'}} />
const Screen4 = () => <View style={{flex: 1, backgroundColor: 'brown'}} />

export default function App() {
  return (
    <NavigationContainer>
      <Nav.Navigator>
        <Nav.Screen name="Tab 1" component={Screen1} />
        <Nav.Screen name="Tab 2" component={Screen2} />
        <Nav.Screen name="Tab 3" component={Screen3} />
        <Nav.Screen name="Tab 4" component={Screen4} />
      </Nav.Navigator>
    </NavigationContainer>
  )
}

// Navigator.js
// this is only slightly modified from https://reactnavigation.org/docs/custom-navigators#usenavigationbuilder

import * as React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import {
  NavigationHelpersContext,
  useNavigationBuilder,
  createNavigatorFactory,
  TabRouter,
  TabActions,
} from '@react-navigation/native';

function TabButton({ route, descriptors, navigation, state }) {
  return (
    <TouchableOpacity
      key={route.key}
      onPress={() => {
        const event = navigation.emit({
          type: 'tabPress',
          target: route.key,
          canPreventDefault: true,
        });

        if (!event.defaultPrevented) {
          navigation.dispatch({
            ...TabActions.jumpTo(route.name),
            target: state.key,
          });
        }
      }}
      style={{ flex: 1 }}
    >
      <Text>{descriptors[route.key].options.title || route.name}</Text>
    </TouchableOpacity>
  )
}

function Navigator({
  initialRouteName,
  children,
  screenOptions,
  tabBarStyle,
  contentStyle,
}) {
  const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
    children,
    screenOptions,
    initialRouteName,
  });

  const renderTab = (route) => (
    <TabButton
      route={route}
      descriptors={descriptors}
      state={state}
      navigation={navigation}
    />
  )

  return (
    <NavigationHelpersContext.Provider value={navigation}>
      <View style={[{ flexDirection: 'row' }, tabBarStyle]}>
        {state.routes.slice(0, 2).map(renderTab)}
      </View>
      <View style={[{ flex: 1 }, contentStyle]}>
        {descriptors[state.routes[state.index].key].render()}
      </View>
      <View style={[{ flexDirection: 'row' }, tabBarStyle]}>
        {state.routes.slice(2).map(renderTab)}
      </View>
    </NavigationHelpersContext.Provider>
  );
}

export const createNavigator = createNavigatorFactory(Navigator);

Upvotes: 1

Related Questions