Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v4] BottomSheetModal in react navigation modal #832

Closed
Livijn opened this issue Feb 2, 2022 · 24 comments
Closed

[v4] BottomSheetModal in react navigation modal #832

Livijn opened this issue Feb 2, 2022 · 24 comments
Labels
bug Something isn't working no-issue-activity

Comments

@Livijn
Copy link

Livijn commented Feb 2, 2022

Bug

The sheet is placed behind the @react-navigation/native modal.

Environment info

Library Version
@gorhom/bottom-sheet 4.1.5
react-native 0.67.2
react-native-reanimated 2.3.1
react-native-gesture-handler 2.2.0

Steps To Reproduce

  1. Create a NativeStack
  2. Place the BottomSheetModalProvider like this:
<NavigationContainer>
  <BottomSheetModalProvider>
    <StackNavigator />
  </BottomSheetModalProvider>
</NavigationContainer>
  1. Use the <BottomSheetModal> in a screen with presentation: "modal".

Describe what you expected to happen:

The sheet is placed behind the modal. I expect the sheet to be in front of the modal that hosts the sheet. I've also tried wrapping the modal-component with the provider, but then I get 'BottomSheetModalInternalContext' cannot be null!.

Reproducible sample code

The snack template throws an error: Cannot convert undefined or null to object

@Livijn Livijn added the bug Something isn't working label Feb 2, 2022
@philipp-wat
Copy link

philipp-wat commented Feb 6, 2022

Hi Livijn,

we had the same bug with "createNativeStackNavigator".

Using "createStackNavigator" from "@react-navigation/stack" fixed it for us.

Hope that helps!
Philipp

@Livijn
Copy link
Author

Livijn commented Feb 7, 2022

I don't want to drop the benefits of using the native stack for this purpose. Thanks though.

@lghimfus
Copy link

hi @Livijn, did you find a workaround for this one?

@Livijn
Copy link
Author

Livijn commented Feb 16, 2022

Nope, not yet. Still waiting for @gorhom to reply.

@lghimfus
Copy link

lghimfus commented Feb 16, 2022

Nope, not yet. Still waiting for @gorhom to reply.

right, I ended up using
<FullWindowOverlay style={StyleSheet.absoluteFill}> from react-native-screens to wrap the bottom sheet; that brings it above the navigators. I found out about it here https://github.com/gorhom/react-native-portal, in case you also need to use a portal

hope this helps a bit in the meantime

@Livijn
Copy link
Author

Livijn commented Feb 17, 2022

If I wrap it like this: <FullWindowOverlay><BottomSheetModal /></FullWindowOverlay> nothing changes. However, if I wrap the content like: <BottomSheetModal><FullWindowOverlay /></BottomSheetModal> the content is placed correctly above the navigators. However, it breaks the animation etc of the sheet.

What way did you do it?

@lghimfus
Copy link

FullWindowOverlay

If I wrap it like this: <FullWindowOverlay><BottomSheetModal /></FullWindowOverlay> nothing changes. However, if I wrap the content like: <BottomSheetModal><FullWindowOverlay /></BottomSheetModal> the content is placed correctly above the navigators. However, it breaks the animation etc of the sheet.

What way did you do it?

<Portal> <FullWindowOverlay style={[StyleSheet.absoluteFill]}> <BottomSheet/>

it might be either that you didn't add style={[StyleSheet.absoluteFill]} or that I'm not using the modal

@Livijn
Copy link
Author

Livijn commented Feb 20, 2022

I did add the correct styling. For now, I guess I'll go with the non-native stack navigator.

@mariomendonca
Copy link

Guys, it worked for me <FullWindowOverlay style={StyleSheet.absoluteFill}><Portal><BottomSheet> {...}

@nihilenz
Copy link

Portal usage didn't help me, I preferred to switch to the non-modal component (BottomSheet) waiting for a better solution with BottomSheetModal

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions
Copy link

github-actions bot commented May 4, 2022

This issue was closed because it has been stalled for 5 days with no activity.

@github-actions github-actions bot closed this as completed May 4, 2022
@thomasgrivet
Copy link

thomasgrivet commented Jul 26, 2022

Hello! I figured a workaround which allow the use of any BottomSheetModal inside react-navigation's native modal screen:

For it to work, you must place a PortalProvider inside your base BottomSheetModalProvider (the one which is usually located inside you App.tsx file), this will be useful for later.
Then, for each modal Screen in which you want to use your BottomSheetModal, you have to place yet another BottomSheetModalProvider and place another PortalProvider inside it. You may also create a generic component which will have the same purpose but cleaner.

Example:

import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import { PortalProvider } from '@gorhom/portal';
import { useMemo } from 'react';

import { NavigationModalScreenContainerContext } from './NavigationModalScreenContainer.context';
import {
    NavigationModalScreenContainerContextProps,
    NavigationModalScreenContainerProps
} from './NavigationModalScreenContainer.types';

export const NavigationModalScreenContainer = ({ children }: NavigationModalScreenContainerProps) => {
    const contextValue = useMemo<NavigationModalScreenContainerContextProps>(
        () => ({
            isNavigationModal: true
        }),
        []
    );

    return (
        <BottomSheetModalProvider>
            <PortalProvider>
                <NavigationModalScreenContainerContext.Provider value={contextValue}>
                    {children}
                </NavigationModalScreenContainerContext.Provider>
            </PortalProvider>
        </BottomSheetModalProvider>
    );
};

Then, wrap every one of your BottomSheetModal inside a Portal component. This will force the sheet to render inside the react-navigation's native modal and not higher in the tree.
From then, your sheets should work as expected with the only downside being that the backdrop won't be able to fill the entire screen (as it is only rendered inside your native modal screen).

See:

My solution was to use a Context.Provider inside NavigationModalScreenContainer with a variable like isNavigationModal, you can use this value to edit your backdrop opacity and / or add shadow if this value is true.
Once again, I advise you to create a generic component for all your BottomSheetModal which contains all the previous components and logic.

Hope this will help, have a nice day ☀️

@wbercx
Copy link

wbercx commented Aug 9, 2022

Adding a <PortalProvider> to the root of my app seemed to interfere with the <BottomSheetModalProvider> that was already there (which already uses PortalProvider under the hood). Only every other bottom sheet I opened would become visible.

I got mine working simply by wrapping my react-navigation modal screen's contents in a <BottomSheetModalProvider>:

function AppStack() {
    return (
        <NativeStack.Navigator>
            {/* ... */}
            <NativeStack.Group screenOptions={{ presentation: 'modal' }}>
                <NativeStack.Screen 
                    name="MyScreenThatNeedsToBeAbleToOpenABottomSheetModal" 
                    component={MyScreenThatNeedsToBeAbleToOpenABottomSheetModal} 
                />
            </NativeStack.Group>
        </NativeStack.Navigator>
    )
}

function MyScreenThatNeedsToBeAbleToOpenABottomSheetModal() {
    return (
        <BottomSheetModalProvider>
            {/* Screen content here, and one of these children can now open a BottomSheetModal */}
        </BottomSheetModalProvider>
    )
}

@magrinj
Copy link
Contributor

magrinj commented Sep 2, 2022

I made a PR #1086 to fix this issue, hope it will be merged soon :)

@nhuesmann
Copy link

Adding a <PortalProvider> to the root of my app seemed to interfere with the <BottomSheetModalProvider> that was already there (which already uses PortalProvider under the hood). Only every other bottom sheet I opened would become visible.

I got mine working simply by wrapping my react-navigation modal screen's contents in a <BottomSheetModalProvider>:

function AppStack() {
    return (
        <NativeStack.Navigator>
            {/* ... */}
            <NativeStack.Group screenOptions={{ presentation: 'modal' }}>
                <NativeStack.Screen 
                    name="MyScreenThatNeedsToBeAbleToOpenABottomSheetModal" 
                    component={MyScreenThatNeedsToBeAbleToOpenABottomSheetModal} 
                />
            </NativeStack.Group>
        </NativeStack.Navigator>
    )
}

function MyScreenThatNeedsToBeAbleToOpenABottomSheetModal() {
    return (
        <BottomSheetModalProvider>
            {/* Screen content here, and one of these children can now open a BottomSheetModal */}
        </BottomSheetModalProvider>
    )
}

Not sure why this works but it does, thank you so much!

@fiizzy
Copy link

fiizzy commented Jan 8, 2024

In my case, instead of using a react native navigation modal screen, I instead used a normal screen, but modified the animation to look like that of a modal. I know this might be the case for some of us.

In other words, instead of

 <Stack.Screen
        name="ModalScreen"
        component={ModalScreen}
        options={{presentation: 'fullScreenModal', headerShown: false}}
      />

I changed the configuration to this:

      <Stack.Screen
        name={ModalScreen}
        component={ModalScreen}
        options={{
          headerShown: false,
          animationTypeForReplace: 'push',
          animation: 'slide_from_bottom',
        }}
      />

Again, this doesn't directly solve the issue, but it is a work around I am very comfortable with, as it doesn't break any existing structure I have in place.
Hope it helps ❤️

@joshsmith
Copy link
Contributor

joshsmith commented Feb 9, 2024

Adding a <PortalProvider> to the root of my app seemed to interfere with the <BottomSheetModalProvider> that was already there (which already uses PortalProvider under the hood). Only every other bottom sheet I opened would become visible.

I got mine working simply by wrapping my react-navigation modal screen's contents in a <BottomSheetModalProvider>:

function AppStack() {
    return (
        <NativeStack.Navigator>
            {/* ... */}
            <NativeStack.Group screenOptions={{ presentation: 'modal' }}>
                <NativeStack.Screen 
                    name="MyScreenThatNeedsToBeAbleToOpenABottomSheetModal" 
                    component={MyScreenThatNeedsToBeAbleToOpenABottomSheetModal} 
                />
            </NativeStack.Group>
        </NativeStack.Navigator>
    )
}

function MyScreenThatNeedsToBeAbleToOpenABottomSheetModal() {
    return (
        <BottomSheetModalProvider>
            {/* Screen content here, and one of these children can now open a BottomSheetModal */}
        </BottomSheetModalProvider>
    )
}

Using this approach in the context of a presentation: 'modal' for me resulted in the backdrop being contained within the screen of that modal (as you might expect). Not a reliable solution if you expect the backdrop to cover the entire screen.

@joshsmith
Copy link
Contributor

Ultimately I did something like:

import { FullWindowOverlay } from 'react-native-screens'

...

const renderContainerComponent = useCallback(
  ({ children }) => <FullWindowOverlay>{children}</FullWindowOverlay>,
  [],
)

...

<BottomSheetModal containerComponent={renderContainerComponent}>

...

@giaset
Copy link

giaset commented Feb 14, 2024

Adding containerComponent={({ children }) => <FullWindowOverlay>{children}</FullWindowOverlay>} to my BottomSheetModal weirdly causes the modal to flicker on open

RPReplay_720.mov

@giaset
Copy link

giaset commented Feb 16, 2024

Fixed by wrapping it in useCallback and only setting it on iOS

const containerComponent = useCallback((props: any) => <FullWindowOverlay>{props.children}</FullWindowOverlay>, []);

return <BottomSheetModal containerComponent={IS_IOS ? containerComponent : undefined}>...</BottomSheetModal>

@danbeo95
Copy link

danbeo95 commented Apr 22, 2024

Ultimately I did something like:

import { FullWindowOverlay } from 'react-native-screens'

...

const renderContainerComponent = useCallback(
  ({ children }) => <FullWindowOverlay>{children}</FullWindowOverlay>,
  [],
)

...

<BottomSheetModal containerComponent={renderContainerComponent}>

...

It works for me
Why it works with FullWindowOverlay?

@levibuzolic
Copy link

levibuzolic commented Apr 23, 2024

Why it works with FullWindowOverlay?

Because React Navigation's native stack renders modals in a new native view above the existing app, so anything rendered in the original root view will appear behind the modal.

Wrapping with a FullWindowOverlay will add another native overlay above the modal, so now there's 3 native root views:

  1. Your primary app
  2. React navigation's modal overlay
  3. BottomSheetModal, wrapped wrapped in a FullWindowOverlay

@danbeo95
Copy link

Why it works with FullWindowOverlay?

Because React Navigation's native stack renders modals in a new native view above the existing app, so anything rendered in the original root view will appear behind the modal.

Wrapping with a FullWindowOverlay will add another native overlay above the modal, so now there's 3 native root views:

  1. Your primary app
  2. React navigation's modal overlay
  3. BottomSheetModal, wrapped wrapped in a FullWindowOverlay

It dose make sense
Thank for your reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working no-issue-activity
Projects
None yet
Development

No branches or pull requests