Search code examples
react-nativereact-navigationreact-native-navigation

Render bottom tab with persistent action sheet


I am trying to achieve the following layout inside my app:

enter image description here

Basically render the content of my <BottomTabNavigator/> inside a bottom sheet while a map is always rendered as a screen in the back. As you tab around the bottom-sheet stays put but the content changes.

I am using react-navigation and @gorhom/bottom-sheet

I am able to achieve this but would like some guidance on how to properly set this up using react-navigation.

I haven't found an official guide or anything so I looked up online and some people mention creating a custom tab bar that renders a BottomSheet on top (This is how I am able to achieve this layout). Something like this:

const CustomTabBar = props => {
  return (
    <React.Fragment>
       <BottomSheet
         snapPoints = {[450, 300, 0]}
         renderContent = {this.renderInner}
         renderHeader = {this.renderHeader}
       />
      <BottomTabBar {...props} />
    </React.Fragment>
  );
};

I would then do conditional rendering inside the modal to display the different screens. This does feel a bit hacky and typing the navigation and state props is a nightmare.

Is there a better way to do this?

I have tried wrapping my <BottomTab /> inside the <BottomSheet /> but that doesn't seem to work.

Is the custom tab bar the right way to do this?

Any help is really appreciated!


Solution

  • As here the custom tabbar has bottom sheet it will be same for all the tab screens and can also make performance issues if we manage the bottom sheet states in the custom bottom tab component.

    Have a try with the below example to achieve the same functionality as shown in the above image.

    import React, {
      forwardRef,
      useEffect,
      useImperativeHandle,
      useMemo,
      useRef,
    } from 'react';
    import {View, Text, StyleSheet} from 'react-native';
    import BottomSheet from '@gorhom/bottom-sheet';
    import {GestureHandlerRootView} from 'react-native-gesture-handler';
    
    import {SafeAreaProvider} from 'react-native-safe-area-context';
    
    import {NavigationContainer} from '@react-navigation/native';
    import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
    
    const BottomTab = createBottomTabNavigator();
    
    const CustomBottomSheet = forwardRef((myProps, ref) => {
      // ref
      const bottomSheetRef = useRef<BottomSheet>(null);
    
      // variables
      const snapPoints = useMemo(() => ['25%'], []);
    
      useImperativeHandle(
        ref,
        () => {
          return {
            open: () => {
              bottomSheetRef.current?.expand();
            },
            close: () => {
              bottomSheetRef.current?.close();
            },
          };
        },
        [],
      );
    
      return (
        <BottomSheet ref={bottomSheetRef} snapPoints={snapPoints}>
          <View style={styles.centeredContent}>
            <Text style={styles.textStyle}>{`inside ${myProps.name}`}</Text>
          </View>
        </BottomSheet>
      );
    });
    
    const TabComponent = (props: any) => {
      const customBottomSheetRef = useRef<any>(null);
    
      useEffect(() => {
        customBottomSheetRef.current?.open();
      }, []);
    
      return (
        <View style={styles.centeredContent}>
          <Text style={styles.textStyle}>{props.route.name}</Text>
          <CustomBottomSheet ref={customBottomSheetRef} name={props.route.name} />
        </View>
      );
    };
    
    function App(): JSX.Element {
      return (
        <GestureHandlerRootView style={styles.safeAreaContainer}>
          <SafeAreaProvider style={styles.safeAreaContainer}>
            <NavigationContainer>
              <BottomTab.Navigator>
                <BottomTab.Screen name="home" component={TabComponent} />
                <BottomTab.Screen name="search" component={TabComponent} />
                <BottomTab.Screen name="explore" component={TabComponent} />
                <BottomTab.Screen name="profile" component={TabComponent} />
              </BottomTab.Navigator>
            </NavigationContainer>
          </SafeAreaProvider>
        </GestureHandlerRootView>
      );
    }
    
    const styles = StyleSheet.create({
      safeAreaContainer: {
        flex: 1,
        backgroundColor: 'white',
      },
      centeredContent: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      },
      textStyle: {
        fontSize: 24,
        fontWeight: 'bold',
      },
    });
    
    export default App;
    

    OUTPUT:

    CODE SNIPPET OUTPUT IMAGE