Open a modal over any screen in a tab bottom navigation flow in React Native

Viewed 6101

I'm facing a problem with react native tabBottom navigation. I have a TabButton customized components that on click renders the modal, but i can't seem to find a way of render that specific modal over the other tabs and not just render as a isolated components.

What i'm trying to achieve is when I click the yellow button it renders the modal, but the tabs like (Home, Help,Adjustments and Graphic) stays like a background for the modal, and not just render the modal in another component.

My costumized TabButton component:

import React from 'react';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Button from './styles';

const TabButton = ({onPress, focused}) => {
  return (
    <TouchableWithoutFeedback onPress={onPress}>
      <Button focused={focused}>
        <Icon name="add" size={35} color={'white'} />
      </Button>
    </TouchableWithoutFeedback>
  );
};

export default TabButton;

tabbutton

My tab.routes.js:

import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import TabButton from '../components/Tab/Button';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Home from '../containers/Home';
import Graphic from '../containers/Graphic';
import Help from '../containers/Help';
import NewTransaction from '../containers/NewTransaction';
import SettingsStack from './settings.routes';

const icons = {
  Home: {
    name: 'home',
  },
  Graphic: {
    name: 'pie-chart',
  },
  NewTransaction: {
    name: 'notifications-none',
  },
  Help: {
    name: 'help-outline',
  },
  Adjustment: {
    name: 'settings',
  },
};

const Tab = createBottomTabNavigator();
const TabRoutes = () => (
  <Tab.Navigator
    initialRouteName="HomeScreen"
    screenOptions={({route, navigation}) => ({
      tabBarIcon: ({color, size, focused}) => {
        if (route.name === 'NewTransaction') {
          return <TabButton focused={focused} onPress={() => navigation.navigate('NewTransaction')} />;
        }
        const {name} = icons[route.name];
        return <Icon name={name} size={size} color={color} />;
      },
    })}
    tabBarOptions={{
      keyboardHidesTabBar: true,
      activeTintColor: '#f8b006',
      inactiveTintColor: '#1C3041',
      style: {
        height: 60,
      },
      iconStyle: {
        marginTop: 5,
      },
      labelStyle: {
        fontSize: 12,
        marginBottom: 10,
      },
    }}>
    <Tab.Screen
      options={{
        title: 'Home',
      }}
      name="Home"
      component={Home}
    />
    <Tab.Screen
      options={{
        title: 'Gráfico',
      }}
      name="Graphic"
      component={Graphic}
    />
    <Tab.Screen
      options={{
        title: '',
      }}
      component={NewTransaction}
      name="NewTransaction"/>
    <Tab.Screen
      options={{
        title: 'Ajuda',
      }}
      name="Help"
      component={Help}
    />
    <Tab.Screen
      options={{
        title: 'Ajustes',
      }}
      name="Adjustment"
      component={SettingsStack}
    />
  </Tab.Navigator>
);

export default TabRoutes;

My modal Component:

import React from 'react';
import RBSheet from 'react-native-raw-bottom-sheet';
import PropTypes from 'prop-types';

const CustomModal = ({children, refRBSheet, borderRadius, height, animationType, onClose, onOpen}) => {
  return (
    <RBSheet
      animationType={animationType}
      height={height}
      onClose={onClose}
      onOpen={onOpen}
      ref={refRBSheet}
      closeOnDragDown={true}
      closeOnPressMask={true}
      customStyles={{
        container: {
          backgroundColor: 'white',
          borderTopLeftRadius: borderRadius,
          borderTopRightRadius: borderRadius,
        },
      }}>
      {children}
    </RBSheet>
  );
};

export default CustomModal;

And my NewTransaction component that renders the modal:

import React, {useRef, useEffect} from 'react';
import {Text, View} from 'react-native';
import CustomModal from '../../components/Modal/';
const NewTransaction = ({isVisible, onClose}) => {
  const refRBSheet = useRef();

  useEffect(() => {
    if (isVisible) {
      refRBSheet.current.open();
    } else {
      refRBSheet.current.close();
    }
  }, [isVisible]);

  return (
    <CustomModal refRBSheet={refRBSheet} onClose={onClose}>
      <View>
        <Text>Hellow</Text>
      </View>
    </CustomModal>
  );
};

export default NewTransaction;

2 Answers

You need to wrap it all inside a RootNavigator as such:

const RootStack = createStackNavigator();

const RootNavigator = () => {
  return (
    <RootStack.Navigator screenOptions={{ headerShown: false, presentation: 'modal', animationEnabled: false }}>
      <RootStack.Screen name="BottomTabNavigatorScreen" component={TabRoutes}/>
      <RootStack.Screen name="CustomModal" component={CustomModal} options={{ animationEnabled: true }}/>
    </RootStack.Navigator>
  )
}

export default () => {
  return (
    <NavigationContainer>
      <RootNavigator />
    </NavigationContainer>
  );
};

You can then add a tabPress listener on the button to navigate to the modal and a placeholder required in the component field:

const placeholder = () => <View />


<Tab.Screen name="NewTransaction" component={placeholder}
    listeners={({ navigation }) => ({
       tabPress: (e) => {
          e.preventDefault();
          navigation.navigate('CustomModal');
       }
    })>
</Tab.Screen>

First, create a new page/screen that returns null so that when you launch modal it doesn't show any background. like this

const MyModalBackgroundScreen = () => {
  return null;
};

after that create Tab in tab navigator

import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import TabButton from '../components/Tab/Button';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Home from '../containers/Home';
import Graphic from '../containers/Graphic';
import Help from '../containers/Help';
import NewTransaction from '../containers/NewTransaction';
import SettingsStack from './settings.routes';

const icons = {
  Home: {
    name: 'home',
  },
  Graphic: {
    name: 'pie-chart',
  },
  NewTransaction: {
    name: 'notifications-none',
  },
  Help: {
    name: 'help-outline',
  },
  Adjustment: {
    name: 'settings',
  },
};

const Tab = createBottomTabNavigator();
const TabRoutes = () => (
  <Tab.Navigator
    initialRouteName="HomeScreen"
    screenOptions={({route, navigation}) => ({
      tabBarIcon: ({color, size, focused}) => {
        //removd that if condition 
        const {name} = icons[route.name];
        return <Icon name={name} size={size} color={color} />;
      },
    })}
    tabBarOptions={{
      keyboardHidesTabBar: true,
      activeTintColor: '#f8b006',
      inactiveTintColor: '#1C3041',
      style: {
        height: 60,
      },
      iconStyle: {
        marginTop: 5,
      },
      labelStyle: {
        fontSize: 12,
        marginBottom: 10,
      },
    }}>
    <Tab.Screen
      options={{
        title: 'Home',
      }}
      name="Home"
      component={Home}
    />
    <Tab.Screen
      options={{
        title: 'Gráfico',
      }}
      name="Graphic"
      component={Graphic}
    />
    <Tab.Screen
      name="Graphic"
      component={MyModalBackgroundScreen} //first import this in this 
      options={{
        tabBarLabel: ({}) => {
          return null; //no label will be displayed
        },
        tabBarButton: () => {
          return <CustomModal refRBSheet={refRBSheet} onClose={onClose}>; // call your modal here directly.
        },
      }}
    />
    <Tab.Screen
      options={{
        title: '',
      }}
      component={NewTransaction}
      name="NewTransaction"/>
    <Tab.Screen
      options={{
        title: 'Ajuda',
      }}
      name="Help"
      component={Help}
    />
    <Tab.Screen
      options={{
        title: 'Ajustes',
      }}
      name="Adjustment"
      component={SettingsStack}
    />
  </Tab.Navigator>
);

export default TabRoutes;
Related