react-navigation-bottom-sheet icon indicating copy to clipboard operation
react-navigation-bottom-sheet copied to clipboard

Functionality for rendering a single modal over a stack

Open codybrouwers opened this issue 3 years ago • 2 comments

Hi there, thank you for the great library, it is a great abstraction over the bottom sheet library for routing!

An issue I came across was needing to show a single bottom sheet in a transparent modal over a screen like this:

const Stack = createNativeStackNavigator();
const BottomSheet = createBottomSheetNavigator();

function MyModal() {
  return (
    <BottomSheet.Navigator>
      <BottomSheet.Screen
        component={MyModalComponent}
        name="MyModal"
        options={MyModalComponent.options}
      />
    </BottomSheet.Navigator>
  );
}

export function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        component={HomeScreen}
        name="Home"
      />
      <Stack.Group screenOptions={{ presentation: "transparentModal" }}>
        <Stack.Screen
          component={MyModal}
          name="MyModalBottomSheet"
        />
      </Stack.Group>
    </Stack.Navigator>
  );
}

If my HomeStack has lots of other screens that I don't want to be shown as bottom sheets then the above I think is the only option. But this library seems to require that the first screen in the bottom sheet navigator be your app content, not a modal. I'm not sure the best solution but I've added the below patch with a firstScreenIsModal option to always show the bottom sheet provider if true.

I'm happy to submit a PR with the below patch if you think it is a good approach but wanted to get your thoughts on it first!

Patch
// in BottomSheetView.tsx
+  const firstScreen = descriptors[state.routes[0].key];
+  const { firstScreenIsModal = false } = firstScreen.options
+
   // Avoid rendering provider if we only have one screen.
-  const shouldRenderProvider = React.useRef(false);
+  const shouldRenderProvider = React.useRef(firstScreenIsModal);
   shouldRenderProvider.current =
     shouldRenderProvider.current || state.routes.length > 1;
 
-  const firstScreen = descriptors[state.routes[0].key];
   return (
     <>
-      {firstScreen.render()}
+      {firstScreenIsModal ? null : firstScreen.render()}
       {shouldRenderProvider.current && (
         <BottomSheetModalProvider>
-          {state.routes.slice(1).map((route) => {
+          {state.routes.slice(firstScreenIsModal ? 0 : 1).map((route) => {
             const { options, navigation, render } = descriptors[route.key];

codybrouwers avatar Sep 11 '22 12:09 codybrouwers

I was thinking maybe passing the app content as a navigator prop instead of using the first screen would be better. This should solve this case.

janicduplessis avatar Sep 12 '22 15:09 janicduplessis

Yep that would work as well!

I'm not sure I'll have time to put together a PR for a little while as my solution above has solved my immediate need but will come back to it later and let you know before I try implementing your idea!

codybrouwers avatar Sep 17 '22 09:09 codybrouwers