dohooo / react-native-reanimated-carousel

🎠 React Native swiper/carousel component, fully implemented using reanimated v2, support to iOS/Android/Web. (Swiper/Carousel)
https://react-native-reanimated-carousel.vercel.app
MIT License
2.85k stars 329 forks source link

When using Pressable inside a horizontal carousel, the scroll is registered as a tap #150

Closed diego-paired closed 2 years ago

diego-paired commented 2 years ago
    "react-native-reanimated": "2.2.4",
    "react-native-reanimated-carousel": "^2.3.2",
    "react-native-gesture-handler": "^2.3.2",

I'm using the Carousel in its default mode horizontally scrolling. When the items rendered use Pressable and the user scrolls horizontally, the scroll happens but a press is also registered when lifting the finger. If I swap Pressable for a Gesture Handler Touchable, the error disappears.

Please advise, thank you.

dohooo commented 2 years ago
    "react-native-reanimated": "2.2.4",
    "react-native-reanimated-carousel": "^2.3.2",
    "react-native-gesture-handler": "^2.3.2",

I'm using the Carousel in its default mode horizontally scrolling. When the items rendered use Pressable and the user scrolls horizontally, the scroll happens but a press is also registered when lifting the finger. If I swap Pressable for a Gesture Handler Touchable, the error disappears.

Please advise, thank you.

Can you show me a video, I can't imagine what it looks like

diego-paired commented 2 years ago

https://user-images.githubusercontent.com/55058765/160386143-73abe6fa-8516-4a1f-917f-77dd29c2c12e.mp4

When pressing to scroll sideways, the pressable is also triggered and navigates to the content of the card

dohooo commented 2 years ago

hmm... show me your code?

dohooo commented 2 years ago

Does that happen if you run my demo code(In homepage)?

jean-sebb commented 2 years ago

Hi @diego-paired and @dohooo, To give maybe a hint, In my case, I have the same problem only with Android when the carousel is under a modal, otherwise the carousel works fine. The problem appear when the carousel is under a modal with a touchable opacity (from react-native library or gesture handler is the same), but i can't scroll the carousel in my case, the onPress action trigger immediately .

The Component under modal

`import React, { useRef } from 'react';
import { Image, View, TouchableOpacity, Text, Dimensions } from "react-native";
import Carousel from 'react-native-reanimated-carousel';
import { colors } from "../utils/const";
import { isIphoneX } from '../utils/isIphoneX';
import { FoodTruckItem } from './Home/FoodTruckItemComponent';

const manPicture = require('../assets/man.png')

export function OrderCancelComponent({ carouselItems, openMenu, closeModal }) {

    const carouseOrderCancellRef = useRef();

    return (
        <View style={{ flex: 1, height: '100%', width: '100%', alignItems: 'center', backgroundColor: 'white' }}>
            <View style={{ height: '60%', width: '80%', justifyContent: 'flex-end' }}>
                <Image style={{ height: '40%', width: '100%', resizeMode: 'contain' }} source={manPicture}></Image>
                <Text style={{ fontSize: 24, fontWeight: 'bold', fontFamily: 'Poppins', color: colors.blueText, width: '100%', textAlign: 'center', marginTop: 20 }}>Oups! Ce véhicule n’est pas disponible</Text>
                <Text style={{ fontSize: 18, fontWeight: '500', fontFamily: 'Poppins', color: colors.blueText, width: '100%', textAlign: 'center', marginTop: 20 }}>Pas de panique, d’autres t’attendent, jette un coup d’oeil à nos suggestions 👇</Text>
            </View>
            <View style={{ flex: 1, paddingBottom: isIphoneX() ? 20 : 0, height: '40%', width: '100%', justifyContent: 'flex-end', alignItems: 'center' }}>
                <Text style={{ fontSize: 16, fontWeight: 'bold', fontFamily: 'Poppins', color: 'black', width: '90%', textAlign: 'left' }}>Nos suggestions :</Text>
                <View style={{ height: 120, width: '100%', justifyContent: 'center', alignItems: 'center' }}>
                    <Carousel
                        ref={carouseOrderCancellRef}
                        width={Dimensions.get('screen').width}
                        height={120}
                        data={carouselItems}
                        renderItem={({ item }) => <FoodTruckItem item={item} openMenuTapped={() => openMenu(item, false)}></FoodTruckItem>}
                        // onSnapToItem={(index) => setMarkerSelected(index)}
                        // onScrollEnd={(prev, current) => setMarkerSelected(current)}
                        loop={true}
                        mode={"parallax"}
                        modeConfig={{ parallaxScrollingScale: 1, parallaxScrollingOffset: 60 }}
                    />
                </View>
                <TouchableOpacity onPress={closeModal} style={{ height: 52, width: '90%', marginTop: 15, marginBottom: 15, justifyContent: 'center', alignItems: 'center', backgroundColor: colors.orange, borderRadius: 10}}>
                    <Text style={{ fontSize: 16, fontWeight: 'bold', color: 'white' }}>Voir la carte</Text>
                </TouchableOpacity>
            </View>
        </View>
    );
}`

The carousel item component

`import React from 'react';
import { Image, View, Text, Dimensions, TouchableOpacity } from "react-native";
import { colors } from "../../utils/const";
import { sharedStyles } from '../../utils/styles';

const halalIcon = require('../../assets/home/halalFood.png')
const screenWidth = Dimensions.get('screen').width

export function FoodTruckItem({ item, openMenuTapped }) {
    return (
        <View style={{ height: 120, width: screenWidth * 0.9, justifyContent: 'center', alignItems: 'center' }}>
            <TouchableOpacity activeOpacity={1} onPress={openMenuTapped} style={{ ...sharedStyles.shadow, shadowRadius: 5, height: 110, width: '90%', borderRadius: 10, backgroundColor: 'white' }}>
                <View style={{ height: '100%', width: '100%', flexDirection: 'row' }}>
                    <View style={{ height: '100%', width: '30%', justifyContent: 'center', alignItems: 'center' }}>
                        <Image source={{ uri: item.truckDetails.logo }} style={{ height: '80%', width: '80%', resizeMode: 'contain' }}></Image>
                    </View>
                    <View style={{ height: '100%', width: '70%' }}>
                        <View style={{ height: '60%', width: '100%', justifyContent: 'center', alignItems: 'flex-start' }}>
                            {item.truckDetails.halal &&
                                < Image source={halalIcon} style={{ flex: 1, position: 'absolute', top: 5, right: 10, resizeMode: 'contain', height: 40, width: 40 }}></Image>
                            }
                            <Text style={{ fontSize: 18, fontWeight: 'bold', color: 'black', width: '70%' }}>{item.truckDetails.name} •</Text>
                            <Text style={{ fontSize: 16, fontWeight: 'normal', color: 'black', width: '80%' }}>{item.truckDetails.short_description}</Text>
                        </View>
                        <View style={{ height: '40%', width: '100%', justifyContent: 'center', alignItems: 'flex-start' }}>
                            <View>
                                <Text style={{ fontSize: 16, fontWeight: 'normal', color: colors.orange, textDecorationLine: 'underline' }}>Voir le menu</Text>
                            </View>
                        </View>
                    </View>
                </View>
            </TouchableOpacity>
        </View >
    );
}`

My package json

`{
  "name": "foodrivrepo",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "@gorhom/bottom-sheet": "^4.1.5",
    "@react-native-async-storage/async-storage": "^1.17.0",
    "@react-native-firebase/analytics": "^14.7.0",
    "@react-native-firebase/app": "^14.7.0",
    "@react-native-firebase/auth": "^14.7.0",
    "@react-native-firebase/database": "^14.7.0",
    "@react-native-firebase/firestore": "^14.7.0",
    "@react-native-firebase/functions": "^14.7.0",
    "@react-native-firebase/messaging": "^14.7.0",
    "@react-native-masked-view/masked-view": "^0.2.6",
    "@react-navigation/drawer": "^6.3.3",
    "@react-navigation/elements": "^1.3.1",
    "@react-navigation/native": "^6.0.8",
    "@react-navigation/stack": "^6.1.1",
    "axios": "^0.26.1",
    "react": "17.0.2",
    "react-native": "0.67.4",
    "react-native-anchor-carousel": "^4.0.1",
    "react-native-date-picker": "^4.2.0",
    "react-native-fast-image": "^8.5.11",
    "react-native-geolocation-service": "^5.3.0-beta.4",
    "react-native-gesture-handler": "2.3.2",
    "react-native-google-places-autocomplete": "^2.4.1",
    "react-native-image-pan-zoom": "^2.1.12",
    "react-native-image-viewing": "^0.2.1",
    "react-native-image-zoom-viewer": "^3.0.1",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-maps": "0.30.1",
    "react-native-modal": "^13.0.1",
    "react-native-permissions": "^3.3.1",
    "react-native-phone-number-input": "^2.1.0",
    "react-native-reanimated": "^2.5.0",
    "react-native-reanimated-carousel": "^2.3.2",
    "react-native-safe-area-context": "^4.2.4",
    "react-native-screens": "^3.13.1",
    "react-native-snap-carousel": "^3.9.1"
  },
  "devDependencies": {
    "@babel/core": "^7.17.8",
    "@babel/runtime": "^7.17.8",
    "@react-native-community/eslint-config": "^3.0.1",
    "babel-jest": "^27.5.1",
    "eslint": "8.12.0",
    "jest": "^27.5.1",
    "metro-react-native-babel-preset": "^0.70.0",
    "react-test-renderer": "17.0.2"
  },
  "jest": {
    "preset": "react-native"
  }
}

I hope this will help you !

dohooo commented 2 years ago

Hi @diego-paired and @dohooo, To give maybe a hint, In my case, I have the same problem only with Android when the carousel is under a modal, otherwise the carousel works fine. The problem appear when the carousel is under a modal with a touchable opacity (from react-native library or gesture handler is the same), but i can't scroll the carousel in my case, the onPress action trigger immediately .

The Component under modal

`import React, { useRef } from 'react'; import { Image, View, TouchableOpacity, Text, Dimensions } from "react-native"; import Carousel from 'react-native-reanimated-carousel'; import { colors } from "../utils/const"; import { isIphoneX } from '../utils/isIphoneX'; import { FoodTruckItem } from './Home/FoodTruckItemComponent';

const manPicture = require('../assets/man.png')

export function OrderCancelComponent({ carouselItems, openMenu, closeModal }) {

const carouseOrderCancellRef = useRef();

return (
    <View style={{ flex: 1, height: '100%', width: '100%', alignItems: 'center', backgroundColor: 'white' }}>
        <View style={{ height: '60%', width: '80%', justifyContent: 'flex-end' }}>
            <Image style={{ height: '40%', width: '100%', resizeMode: 'contain' }} source={manPicture}></Image>
            <Text style={{ fontSize: 24, fontWeight: 'bold', fontFamily: 'Poppins', color: colors.blueText, width: '100%', textAlign: 'center', marginTop: 20 }}>Oups! Ce véhicule n’est pas disponible</Text>
            <Text style={{ fontSize: 18, fontWeight: '500', fontFamily: 'Poppins', color: colors.blueText, width: '100%', textAlign: 'center', marginTop: 20 }}>Pas de panique, d’autres t’attendent, jette un coup d’oeil à nos suggestions 👇</Text>
        </View>
        <View style={{ flex: 1, paddingBottom: isIphoneX() ? 20 : 0, height: '40%', width: '100%', justifyContent: 'flex-end', alignItems: 'center' }}>
            <Text style={{ fontSize: 16, fontWeight: 'bold', fontFamily: 'Poppins', color: 'black', width: '90%', textAlign: 'left' }}>Nos suggestions :</Text>
            <View style={{ height: 120, width: '100%', justifyContent: 'center', alignItems: 'center' }}>
                <Carousel
                    ref={carouseOrderCancellRef}
                    width={Dimensions.get('screen').width}
                    height={120}
                    data={carouselItems}
                    renderItem={({ item }) => <FoodTruckItem item={item} openMenuTapped={() => openMenu(item, false)}></FoodTruckItem>}
                    // onSnapToItem={(index) => setMarkerSelected(index)}
                    // onScrollEnd={(prev, current) => setMarkerSelected(current)}
                    loop={true}
                    mode={"parallax"}
                    modeConfig={{ parallaxScrollingScale: 1, parallaxScrollingOffset: 60 }}
                />
            </View>
            <TouchableOpacity onPress={closeModal} style={{ height: 52, width: '90%', marginTop: 15, marginBottom: 15, justifyContent: 'center', alignItems: 'center', backgroundColor: colors.orange, borderRadius: 10}}>
                <Text style={{ fontSize: 16, fontWeight: 'bold', color: 'white' }}>Voir la carte</Text>
            </TouchableOpacity>
        </View>
    </View>
);

}`

The carousel item component

`import React from 'react'; import { Image, View, Text, Dimensions, TouchableOpacity } from "react-native"; import { colors } from "../../utils/const"; import { sharedStyles } from '../../utils/styles';

const halalIcon = require('../../assets/home/halalFood.png') const screenWidth = Dimensions.get('screen').width

export function FoodTruckItem({ item, openMenuTapped }) { return ( <View style={{ height: 120, width: screenWidth * 0.9, justifyContent: 'center', alignItems: 'center' }}> <TouchableOpacity activeOpacity={1} onPress={openMenuTapped} style={{ ...sharedStyles.shadow, shadowRadius: 5, height: 110, width: '90%', borderRadius: 10, backgroundColor: 'white' }}> <View style={{ height: '100%', width: '100%', flexDirection: 'row' }}> <View style={{ height: '100%', width: '30%', justifyContent: 'center', alignItems: 'center' }}> <Image source={{ uri: item.truckDetails.logo }} style={{ height: '80%', width: '80%', resizeMode: 'contain' }}> <View style={{ height: '100%', width: '70%' }}> <View style={{ height: '60%', width: '100%', justifyContent: 'center', alignItems: 'flex-start' }}> {item.truckDetails.halal && < Image source={halalIcon} style={{ flex: 1, position: 'absolute', top: 5, right: 10, resizeMode: 'contain', height: 40, width: 40 }}> } <Text style={{ fontSize: 18, fontWeight: 'bold', color: 'black', width: '70%' }}>{item.truckDetails.name} • <Text style={{ fontSize: 16, fontWeight: 'normal', color: 'black', width: '80%' }}>{item.truckDetails.short_description} <View style={{ height: '40%', width: '100%', justifyContent: 'center', alignItems: 'flex-start' }}> <Text style={{ fontSize: 16, fontWeight: 'normal', color: colors.orange, textDecorationLine: 'underline' }}>Voir le menu ); }`

My package json

{ "name": "foodrivrepo", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint ." }, "dependencies": { "@gorhom/bottom-sheet": "^4.1.5", "@react-native-async-storage/async-storage": "^1.17.0", "@react-native-firebase/analytics": "^14.7.0", "@react-native-firebase/app": "^14.7.0", "@react-native-firebase/auth": "^14.7.0", "@react-native-firebase/database": "^14.7.0", "@react-native-firebase/firestore": "^14.7.0", "@react-native-firebase/functions": "^14.7.0", "@react-native-firebase/messaging": "^14.7.0", "@react-native-masked-view/masked-view": "^0.2.6", "@react-navigation/drawer": "^6.3.3", "@react-navigation/elements": "^1.3.1", "@react-navigation/native": "^6.0.8", "@react-navigation/stack": "^6.1.1", "axios": "^0.26.1", "react": "17.0.2", "react-native": "0.67.4", "react-native-anchor-carousel": "^4.0.1", "react-native-date-picker": "^4.2.0", "react-native-fast-image": "^8.5.11", "react-native-geolocation-service": "^5.3.0-beta.4", "react-native-gesture-handler": "2.3.2", "react-native-google-places-autocomplete": "^2.4.1", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-viewing": "^0.2.1", "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-maps": "0.30.1", "react-native-modal": "^13.0.1", "react-native-permissions": "^3.3.1", "react-native-phone-number-input": "^2.1.0", "react-native-reanimated": "^2.5.0", "react-native-reanimated-carousel": "^2.3.2", "react-native-safe-area-context": "^4.2.4", "react-native-screens": "^3.13.1", "react-native-snap-carousel": "^3.9.1" }, "devDependencies": { "@babel/core": "^7.17.8", "@babel/runtime": "^7.17.8", "@react-native-community/eslint-config": "^3.0.1", "babel-jest": "^27.5.1", "eslint": "8.12.0", "jest": "^27.5.1", "metro-react-native-babel-preset": "^0.70.0", "react-test-renderer": "17.0.2" }, "jest": { "preset": "react-native" } }

I hope this will help you !

Could you give me an REPO or EXPO link? It'll make it easier for me to deal with your problem.

jean-sebb commented 2 years ago

Hi again, sorry for the late answer i found the solution !

In my case it's because of the modal, explanation just below:

`Usage with modals on Android#
On Android RNGH does not work by default because modals are not located under React Native Root view in native hierarchy. In order to make it workable, components need to be wrapped with gestureHandlerRootHOC (it's no-op on iOS and web).

E.g.

const ExampleWithHoc = gestureHandlerRootHOC(function GestureExample() {
  return (
    <View>
      <DraggableBox />
    </View>
  );
});

export default function Example() {
  return (
    <Modal animationType="slide" transparent={false}>
      <ExampleWithHoc />
    </Modal>
  );
}`

Hope this will help someone maybe ! Link to react native gesture handler doc : https://docs.swmansion.com/react-native-gesture-handler/docs/installation/#usage-with-modals-on-android

jean-sebb commented 2 years ago

currently experiencing the same. just migrated from react-native-snap-carousel, but am facing this issue that i just can't figure out how to fix. i also get a warning that my react-native-gesture-handler is using an old api version, which may have something to do with it. i'm using a bare react-native app, tested on both Android and iOS, with Pressable and TouchOpacity, and both trigger the onPress function when scrolling. I'm not using the modal component, by the way

Maybe try to add gestureHandlerRootHOC on your component and use TouchOpacity from react-native-gesture-handler inside your carousel item.

About the warning i don't know exactly what is it, but after some research it seems to be nothing important. In my case i remove the warning message by downgrading to "react-native-gesture-handler": "2.1.1". but keep in mind that this is only a warning.

If you use react navigation library, it's maybe because of @react-navigation/stack using old api version. Now they use native stack @react-navigation/native-stack

dohooo commented 2 years ago

@diego-paired @halaxysounds

I have reproduced your problem. I used the same version. @diego-paired

"react-native-reanimated": "2.2.4",
"react-native-reanimated-carousel": "^2.3.2",
"react-native-gesture-handler": "^2.3.2",
// will be trigger the onPress function when scrolling
import { TouchableOpacity } from 'react-native';

// will be fine
import { TouchableOpacity } from 'react-native-gesture-handler';

Can you give it a try and let me know the result? I believe this is solvable, because most people haven't talked to me about this kind of problem, and they've obviously used the solution here.

dohooo commented 2 years ago

When I changed to TouchableOpacity of RNGH , the error no longer appeared.

https://user-images.githubusercontent.com/32405058/161354100-0c0e518e-165c-408e-8a3c-82ac672e2017.mov

lehresman commented 2 years ago

Is there an answer for this that does not use TouchableOpacity from react-native-gesture-handler? It is not fully compatible with React Native's TouchableOpacity and will not work in my use case.

Does react-native-reanimated-carousel not work with TouchableOpacity components nested inside?

dohooo commented 2 years ago

Is there an answer for this that does not use TouchableOpacity from react-native-gesture-handler? It is not fully compatible with React Native's TouchableOpacity and will not work in my use case.

Does react-native-reanimated-carousel not work with TouchableOpacity components nested inside?

What's your mean? If you have any other questions. I suggest you create a new issue.

lehresman commented 2 years ago

The TouchableOpacity component provided by react-native-gesture-handler is not a drop-in replacement for React Native's TouchableOpacity component. It behaves differently and handles styling differently. I'm unfortunately not able to use their TouchableOpacity component as was suggested in this ticket. But I'm still having the same problem as the original poster in this ticket. That is, when I have a component inside of a carousel, swiping left/right is also registered as a tap event by TouchableOpacity, erroniously. Is there any other way around this issue?

dohooo commented 2 years ago

The TouchableOpacity component provided by react-native-gesture-handler is not a drop-in replacement for React Native's TouchableOpacity component. It behaves differently and handles styling differently. I'm unfortunately not able to use their TouchableOpacity component as was suggested in this ticket. But I'm still having the same problem as the original poster in this ticket. That is, when I have a component inside of a carousel, swiping left/right is also registered as a tap event by TouchableOpacity, erroniously. Is there any other way around this issue?

Sry I don't know. Maybe you can create a discussion for this question.

dohooo commented 2 years ago

The TouchableOpacity component provided by react-native-gesture-handler is not a drop-in replacement for React Native's TouchableOpacity component. It behaves differently and handles styling differently. I'm unfortunately not able to use their TouchableOpacity component as was suggested in this ticket. But I'm still having the same problem as the original poster in this ticket. That is, when I have a component inside of a carousel, swiping left/right is also registered as a tap event by TouchableOpacity, erroniously. Is there any other way around this issue?

Infact I don’t think it related to this library, and I think you can find some inspiration from documentation in react-native-gesture-handler library.

lucianobracco-geojam commented 1 year ago

Hey there, thanks for this amazing library. I am still facing this issue

silverit commented 6 months ago

@diego-paired @halaxysounds

I have reproduced your problem. I used the same version. @diego-paired

"react-native-reanimated": "2.2.4",
"react-native-reanimated-carousel": "^2.3.2",
"react-native-gesture-handler": "^2.3.2",
// will be trigger the onPress function when scrolling
import { TouchableOpacity } from 'react-native';

// will be fine
import { TouchableOpacity } from 'react-native-gesture-handler';

Can you give it a try and let me know the result? I believe this is solvable, because most people haven't talked to me about this kind of problem, and they've obviously used the solution here.

Thank you! You save my life 😆