react-native-gesture-handler: Doesn't work in a modal

Hi ! I have a little issue on Android, onGestureEvent is not trigger when GestureHandler components is on a modal on Android. When I change the modal to a View, it works perfectly šŸ‘Œ

No problem on iOS

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 93
  • Comments: 101 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I got this working using the suggestion for Library authors

<Modal>
    <GestureHandlerRootView style={{width:'100%', height:'100%'}}>
        <myGestureEnabledComponent/>
    </GestureHandlerRootView>
</Modal>

Only tested on my Pixel 5 so far, but things are looking good so far šŸ˜ƒ

A little trick to solve it inside a modal:

import { TouchableWithoutFeedback } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
<TouchableWithoutFeedback onPress={...}
    <RectButton>
         ...
    </RectButton>
</TouchableWithoutFeedback>

RNGH works in a react native Modal if you wrap ONLY the content of the Modal in the GestureHandlerRootView component (see RNGH docs, this comment and this pull request).

But if you wrap the whole app (or a screen that contains the Modal) in GestureHandlerRootView too, RNGH buttons stops working inside the Modal (see why below).

Examples

Example of GestureHandlerRootView used only inside a Modal:

const GHRVInsideModal: React.FC = () => {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <TouchableOpacity onPress={() => setVisible(true)}>
        <Text>Open (it's RN button) - it works</Text>
      </TouchableOpacity>

      <Modal
        animationType="slide"
        transparent
        visible={visible}
      >
        <View style={styles.modal}>
          <RectButton onPress={() => setVisible(false)}>
            <Text>Close (it's RNGH button outside GestureHandlerRootView) - it does not work</Text>
          </RectButton>

          <GestureHandlerRootView>
            <RectButton onPress={() => setVisible(false)}>
              <Text>Close (it's RNGH button inside GestureHandlerRootView) - it works</Text>
            </RectButton>
          </GestureHandlerRootView>
        </View>
      </Modal>
    </>
  );
};

Example of nested GestureHandlerRootView:

const NestedGHRV: React.FC = () => {
  const [visible, setVisible] = useState(false);

  return (
    <GestureHandlerRootView>
      <RectButton onPress={() => setVisible(true)}>
        <Text>Open (it's RNGH button) - it works</Text>
      </RectButton>

      <Modal
        animationType="slide"
        transparent
        visible={visible}
      >
        <View style={styles.modal}>
          <GestureHandlerRootView>
            <RectButton onPress={() => setVisible(false)}>
              <Text>Close (it's RNGH button inside GestureHandlerRootView) - it does not work</Text>
            </RectButton>

            <TouchableOpacity onPress={() => setVisible(false)}>
              <Text>Close (it's RN button) - it works</Text>
            </TouchableOpacity>
          </GestureHandlerRootView>
        </View>
      </Modal>
    </GestureHandlerRootView>
  );
};

Video with two examples:

https://user-images.githubusercontent.com/35278731/123794634-b9bcbb80-d8eb-11eb-9780-bb7713652b33.mov

See this repo with two examples.

React navigation

If you create a Modal inside react-navigationā€™s stack and wrap the content of the Modal in GestureHandlerRootView as the documentation says, it reproduces the second example and RNGH buttons in a Modal will not work.

This is happening because react-navigation wraps the stack content in GestureHandlerRootView and your GH root view in a Modal renders as a simple View.

How to fix

Why does the button in the Modal work in the first case, but not in the second?

If we look at the 17th line of the GestureHandlerRootView.android.tsx file we see, that nested gesture handler root views renders as View. If you try to delete 17-22 lines from the GestureHandlerRootView.android.tsx file the second example will start working.

Video reproduced what will be if you delete 17-22 lines from the GestureHandlerRootView.android.tsx file (uploaded to YouTube, because GitHub does not allow uploading large files): RNGH: nested GestureHandlerRootView renders as View

As a result, if the nested GestureHandlerRootView is NOT rendered as a simple View, RNGH buttons in a Modal in Android will work. I created a pull request https://github.com/software-mansion/react-native-gesture-handler/pull/1493 that fixes it.

Few libraries requires us to deal with native context, if so:

For android, we will need a special care as stated in RNGH document:

Update your MainActivity.java file Screen Shot 2019-11-30 at 12 17 07 PM

Hope it helps

@osdnk is this fixable?

The use of react-native-portalize fixed this perfectly for me while using React Navigation - here is a fully working Expo Snack for anyone that wants a minimal example of how to get this working in their own project: https://snack.expo.dev/@thomascoldwell/react-navigation---rngh-root-view-bug-android---portal-fix

+1 gesture does not work on modal

Still not working on Android šŸ‘Ž

@gideaoms that tricks may works for button but my use case is to use PinchGestureHandler inside the modal.

A little trick to solve it inside a modal:

import { TouchableWithoutFeedback } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
<TouchableWithoutFeedback onPress={...}
    <RectButton>
         ...
    </RectButton>
</TouchableWithoutFeedback>

@deflorilemarului @kesha-antonov @fauker Will spare you some time, neither Modal, nor Modal-based packages (if they donā€™t require you to link native dependencies - they are based on react-nativeā€™s Modal) will help you (Iā€™ve tried all of them, basically). All of gesture- related components (bottomsheet-behaviour for us) will not register any touches/swipes etc when rendered inside <Modal>...</Modal> on android; There is 3 solutions I can think of:

  1. Use any other library if you are using modals. You may require rewrite some swipe-based components to PanResponder; this on is pain, but pretty straightforward;
  2. Use modals that are portals. They are not actually portals nor modals- portals are not supported by react-native because there is single application host - but they are working by placing Host Component somewhere up your application and renders Modal content in absolute view above app stack; this one is working solution, but you will loose any useContext() calls due to children being rendered above context providers; does not work for us, since navigation context is lost;
  3. Use modals provided by navigation library (react-navigation); this one works, but API isā€¦ not good - for one you could not make a single screen a modal, only a stack can be in modal mode; this one is solution I stick with.

Well, there is final solution - patch native android implementation (not possible if you are using expo) or wait for patch in a library and then wait until it merged into expo.

p.s. Donā€™t get me wrong, this library is amazing, and I cannot stress enough how much I appreciate work being poured into this and how much better the experience for end user, but bugs like this one make me want to pull my hairs out.

+1. i use reanimated-bottom-sheet with modal

+1 any workaround?

To sum up:

  1. āŒ RNGH does not work in react-nativeā€™s <Modal> component (even though the Component is wrapped in a gestureHandlerRootHOC)
  2. āœ… RNGH does work in a screen from wix/react-native-navigationā€™s which is shown using Navigation.showModal (even though the Screen is wrapped in a gestureHandlerRootHOC)
  3. āœ… RNGH apparently does work if shown using react-navigationā€™s push function with modal: true, but only when not using the native stack (enableScreens()/createNativeStackNavigator()).

I can provide more details if required.

This is still a problem on 1.6.0

@kmagiera @osdnk any update on this? even Touchable*'s donā€™t work inside the modal and this is frustrating

I also ran into this bug today.

import {Platform, Modal} from ā€˜react-nativeā€™; import {gestureHandlerRootHOC} from ā€˜react-native-gesture-handlerā€™; import {AnimatedBottomSheet} from ā€˜ā€¦/AnimatedBottomSheetā€™;

const AnimatedBottomSheetWrapper = Platform.OS === ā€˜androidā€™ ? gestureHandlerRootHOC(AnimatedBottomSheet) : AnimatedBottomSheet;

I have dug into it for a while.

  1. Itā€™s a problem of RNGH. Sorry šŸ˜’
  2. It happens bc modals are not rendered below an RN root view.
  3. Itā€™s fixable by replacing modalsā€™ mechanism in a similar way as we do with an RNRootView by wrapping it with some extra logic. See https://github.com/kmagiera/react-native-gesture-handler/blob/master/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.java
  4. I have no time to do it now, itā€™s time-consuming work and probably requires copying some code in an RNGH core on Android native side.

If someone wants and has time to do it, Iā€™ll happily review it and merge immediately if it would be workable and not too hacky.

@martinezguillaume @mordaha @csto @mars-lan @ParhamZare @ewendel @Via-profit @Dmitrylolo @LaVielle

I got this working using the suggestion for Library authors

<Modal>
    <GestureHandlerRootView style={{width:'100%', height:'100%'}}>
        <myGestureEnabledComponent/>
    </GestureHandlerRootView>
</Modal>

Only tested on my Pixel 5 so far, but things are looking good so far

Works for nexus5x emulator.

not worked when using stack of react-navigation

This issue is more than 2 years old, are there any plans to fix this?

Iā€™ve created custom Slider components for my app and canā€™t use them at all on Android, since a lot of views in my app are Modals

Iā€™ve wrapped every screen and Modal component in a gestureHandlerRootHoC (using wix/react-native-navigation)

A little trick to solve it inside a modal:

import { TouchableWithoutFeedback } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
<TouchableWithoutFeedback onPress={...}
    <RectButton>
         ...
    </RectButton>
</TouchableWithoutFeedback>

Hey @gideaoms . I wonder how did your comment went unnoticed here. It definitely does the trick. Thanks man!

Try this: https://docs.swmansion.com/react-native-gesture-handler/docs/installation/#usage-with-modals-on-android

const ExampleWithHoc = gestureHandlerRootHOC(() => (
    <View>
      <DraggableBox />
    </View>
  );
);

export default function Example() {
  return (
    <Modal>
      <ExampleWithHoc />
    </Modal>
  );
}

@DavidAPears have you tried wrapping you modals in gestureHandlerRootHOC ?

something like this ?


const ModalInner = gestureHandlerRootHOC(function GestureExample() {
  return (
    <View>
      { RNGH components . }
    </View>
  );
});

export default function ModalForX() {
  return (
    <Modal animationType="slide" transparent={false}>
      <ModalInner />
    </Modal>
  );
}


Iā€™ve played around a bit and was able to find the issue. However, I am not sure whatā€™s causing it and how to fix it.

The problem in my case is, that I react-native-gesture-handler will not work inside a @react-navigation/stack, if placed inside a modal (so basically Stack.Navigator > SomeScreenComponent > Modal > gestureHandlerRootHOC(PanGestureHandler) fails).

If I cut the Stack Navigator out or use the Tab Navigator instead, it works like a charm, so I am pretty sure itā€™s the Stack Navigatorsā€™ fault.

Relevant package versions:

react-native@0.61.5
@react-navigation/native@5.0.9
@react-navigation/stack@5.1.1

Iā€™ll try to setup a demo repo ASAP, let me know if I can provide any more helpful info.

Weā€™re planning on releasing the next version in a month or so. This is a transition from 1.x to 2.x so it may take a bit longer than usual.

@jvaclavik I used it in conjunction with https://github.com/callstack/react-native-paper, which has a component called Portal designed specifically for this purpose.

same error.

I got this working using the suggestion for Library authors

<Modal>
    <GestureHandlerRootView style={{width:'100%', height:'100%'}}>
        <myGestureEnabledComponent/>
    </GestureHandlerRootView>
</Modal>

Only tested on my Pixel 5 so far, but things are looking good so far

Works for nexus5x emulator.

not worked when using stack of react-navigation

Thanks its working for me

To sum up:

  1. āŒ RNGH does not work in react-nativeā€™s <Modal> component (even though the Component is wrapped in a gestureHandlerRootHOC)
  2. āœ… RNGH does work in a screen from wix/react-native-navigationā€™s which is shown using Navigation.showModal (even though the Screen is wrapped in a gestureHandlerRootHOC)
  3. āœ… RNGH apparently does work if shown using react-navigationā€™s push function with modal: true, but only when not using the native stack (enableScreens()/createNativeStackNavigator()).

I can provide more details if required.

if you had examples it would be great.

setting coverSreen={false} on modal works. But need my modal cover the screen

Here is a demo app to (almost blank from npx react-native init), that will show the issue. You can toggle the stack navigator inside the app and see how the PanGestureHandler becomes functional and unfunctional.

https://github.com/mxmzb/react-native-breakable-app

whop #937

Thatā€™s amazing! It still doesnā€™t work with react-native-modal for me, though!

// edit: Actually, default modal doesnā€™t work either, but it should, starting from 1.6.0 if I am not mistaken? Will try to find out whatā€™s going wrong

still not working if change modal to view everything work just fineā€¦

For me, I just installed the newest version 2.4.0. And then I use GestureHandlerRootView to wrapper the PanGestureHandler. It works. Remember that donā€™t wrapper the view or others components. Wrapper thePanGestureHandler like below:

 const $panelView = (
    <GestureHandlerRootView>
      <PanGestureHandler onGestureEvent={onGestureEvent}>
        <Animated.View style={[styles.panelView, panelStyle]}>
          {$contentView}
        </Animated.View>
      </PanGestureHandler>
    </GestureHandlerRootView>
  )


@jakub-gonet When are you planning the next release with #1603 changes?

After a whole day of debugging

  1. You need to wrap you RNGH components in gestureHandlerRootHOC
  2. Upgrade from v1.8.0 to v1.10.3
  3. Wrap the modal in react-native-paperā€™s <Portal>
  4. Make sure the react native paper provider is the first component to render

When I did this it did not work:

Screen Shot 2021-04-04 at 9 47 18 PM

However this worked:

Screen Shot 2021-04-04 at 9 52 28 PM

if i skip any one of these steps it does not work

I got this working using the suggestion for Library authors

<Modal>
    <GestureHandlerRootView style={{width:'100%', height:'100%'}}>
        <myGestureEnabledComponent/>
    </GestureHandlerRootView>
</Modal>

Only tested on my Pixel 5 so far, but things are looking good so far

Works for nexus5x emulator.

I have a similar issue. It seems that the Stack.Navigator doesnā€™t work well on Android. It doesnā€™t matter if I use a mode=ā€œmodalā€ or ā€œcardā€.

So i was playing around and had the following code

import { SafeAreaView, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'

import { enableScreens } from 'react-native-screens';
enableScreens(); // <-- this fucked it up

const Screen1 = ({ navigation }) => {
    return (
      <SafeAreaView style={{ flex: 1 }}>
        <Button title="Open Modal" onPress={() => navigation.push('Modal')} />
      </SafeAreaView>
    )
  }

  const Screen2 = ({ navigation }) => {
    return (
      <SafeAreaView style={{ flex: 1 }}>
        <Button title="Close Modal" onPress={() => navigation.goBack()} />
      </SafeAreaView>
    )
  }

const App = () => {

return (
        <NavigationContainer>
          <Stack.Navigator headerMode="none">
            <Stack.Screen name="Main" component={Screen1} />
            <Stack.Screen name="Modal" component={Screen2} />
          </Stack.Navigator>
        </NavigationContainer>
  )

}

Then I removed this line; enableScreens();

And then it worked correctly on Android.

After trying #937 I found out it didnā€™t work for meā€¦

Maybe itā€™s because Iā€™m using wixā€™s react-native-navigation? As far as I know each and every screen is registered with the gestureHandlerRootHOC (the library works perfectly with normal, non modal views).

This is a fragment of the screen in which I tried the RectButton in a Modal (basically as it is in the doc update)

  renderSearchScreen = () => {
    const { showSearchHistory } = this.state;
    const ExampleWithHoc = gestureHandlerRootHOC(() => {
      return (
        <View style={genericStyles.container}>
          <SearchScreen
            searchBar={{
              ...this.searchBar,
              searchQuery: this.props.searchQuery,
            }}
            onBackPress={() => {
              this.setState({ showSearchHistory: false });
            }}
          />
        </View>
      );
    });

    if (showSearchHistory) {
      return (
        <RNModal
          animationType="fade"
          transparent
          visible={this.state.showSearchHistory}
          onRequestClose={() => {}}>
          <ExampleWithHoc />
        </RNModal>
      );
    }

    return null;
  };

The modal loads when itā€™s supposed to, but the RectButton does not fire the onPress event. I tried making a minimum reproducible demo with a brand new app but then I ran into #848 #676 #835 (possible duplicates of one another).

This issue seems to be related to https://github.com/kmagiera/react-native-screens/issues/61

Donā€™t use react native screens in android. Tested and working fine with ā€˜reanimated-bottom-sheetā€™

if (Platform.OS === ā€˜iosā€™) { useScreens(); }

This React Navigation Modal config for transparent bg is

ScreenOne: { screen: ScreenOne, navigationOptions: { gesturesEnabled: false }, } { mode: ā€˜modalā€™, transparentCard: true, headerMode: ā€˜noneā€™, cardStyle: { backgroundColor: ā€˜transparentā€™, opacity: 1 }, transitionConfig: () => ({ containerStyle: { backgroundColor: ā€˜transparentā€™, } }) }

I was using a standard React Native Modal and was experiencing this issue. I worked around the problem by creating a new screen and displaying it as a modal. Iā€™m using react-native-navigation, so:

Navigation.showModal({
    component: {
      name: navRoutes.ImageModal,
      passProps: { image },
    },
  })

Gestures are working as expected on both iOS and Android, and I still get transparent background that I wanted from my original modal.

setting coverSreen={false} on modal works. But need my modal cover the screen

It works for me, thanks. If you want to cover the screen , you can make a customed modal componnet and put it at the top level of component tree, then set style to cover screen, you can set children component (modal content) by Ref.

not worked when using stack of react-navigation

setting coverSreen={false} on modal works. But need my modal cover the screen

It works for me, thanks. If you want to cover the screen , you can make a customed modal componnet and put it at the top level of component tree, then set style to cover screen, you can set children component (modal content) by Ref.

Combination of Portal from react-native-portalize and gestureHandlerRootHOC works for me. Something like that:

<Portal>
   <Modal>
      <GestureHandlerRootHOCWrappedComponent />
   </Modal>
</Portal>

Its working for me: <TouchableWithoutFeedback onPress={() => { console.log("press"); }} > <Text> <RectButton> ... </RectButton> </Text> </TouchableWithoutFeedback>

@waheedakhtar694 , itā€™s using absolute. At least in my case, no problem šŸ˜

@flyskywhy if react-native-modal-animated doesnā€™t use a separate activity like react-native-modal then how itā€™s render on top of everything? If itā€™s using an absolute position then it could make a problem in some cases.

Tried every suggestions mentioned above but still no luck yet. Please help its really frustrating. My react native version in 0.62.2 tried with both /react-native-gesture-handler 1.5.6 & 1.6.0.

@cristianoccazinsp It only uses RNā€™s modal to cover the screen, which can be disabled by setting coverScreen to false

Same for me. Tested react-nativeā€™s PanResponder handlers on modal - works fine.

I have a thoughts about this: when we are linking this library to android project, we are doing next step

@Override
            protected ReactRootView createRootView() {
                return new RNGestureHandlerEnabledRootView(MainActivity.this);
            }

And we know that modal is separate package. May be theres sense to do something same with it?