react-native-gesture-handler icon indicating copy to clipboard operation
react-native-gesture-handler copied to clipboard

Swipeable renderLeftActions button not firing

Open erie-e9 opened this issue 1 year ago • 28 comments

Description

Trying with simple example, I faced next issue, I can't trigger onPress action on renderLeftActions, only renderRightActions works fine. Even if I momentarily swap the body of both, the problem persists. This is working fine in v2.20.2, but something crashes in 2.21.0, 2.21.1 and 2.21.2.

Video.

https://github.com/user-attachments/assets/2933b5a9-ab89-418d-841d-631fe761b760

Steps to reproduce

  1. Implement Swipeable inside a GestureHandlerRootView, with two levels of TouchableOpacity children.
  2. Swipe on the Swipeable. Both right and left TouchableOpacity can be displayed.
  3. Once the button is displayed, try to trigger onPress action.
  4. Only right onPress action works, left one seems to be blocked or under-indexed, onPress action doesn't work.

Snack or a link to a repository

https://snack.expo.io/

Gesture Handler version

2.21.0, 2.21.1 and 2.21.2

React Native version

0.76.2

Platforms

iOS

JavaScript runtime

None

Workflow

React Native (without Expo)

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 16 Pro simulator

Acknowledgements

Yes

erie-e9 avatar Nov 18 '24 21:11 erie-e9

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

github-actions[bot] avatar Nov 18 '24 21:11 github-actions[bot]

import React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';

export default function Example() {
  return (
    <View style={{ flex: 1, marginTop: 200, backgroundColor: 'red' }}>
      <Swipeable
        onSwipeableWillClose={(dir) => {
          console.log('will close', dir);
        }}
        onSwipeableWillOpen={(dir) => {
          console.log('will open', dir);
        }}
        onSwipeableOpen={(dir) => {
          console.log('open', dir);
        }}
        onSwipeableClose={(dir) => {
          console.log('close', dir);
        }}
        leftThreshold={50}
        rightThreshold={50}
        renderLeftActions={() => {
          return (
            <TouchableOpacity
              onPress={() => {
                console.log('press outer');
              }}
            >
              <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
                <Text>Left</Text>
              </View>
            </TouchableOpacity>
          );
        }}
        renderRightActions={() => {
          return (
            <TouchableOpacity
              onPress={() => {
                console.log('press outer');
              }}
            >
              <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
                <Text>Right</Text>
              </View>
            </TouchableOpacity>
          );
        }}
      >
        <TouchableOpacity
          onPress={() => {
            console.log('press outer');
          }}
        >
          <View
            style={{
              width: '100%',
              height: 80,
              backgroundColor: 'blue',
            }}
          >
            <TouchableOpacity
              onPress={() => {
                console.log('press inner');
              }}
              style={{ width: 80, height: 80, alignSelf: 'center' }}
            >
              <View style={{ width: 80, height: 80, backgroundColor: 'white' }} />
            </TouchableOpacity>
          </View>
        </TouchableOpacity>
      </Swipeable>
    </View>
  );
}

erie-e9 avatar Nov 18 '24 21:11 erie-e9

Hi, could you clarify the repro you used to test this issue? The sample you provided doesn't have interactive elements in the side action panels, so I added my own. I can confirm the bug exists, but you also mentioned it "worked fine in 2.20.2." I couldn't find any interactive elements that worked better in version 2.20.2 than in 2.21.1. Can you let me know which interactive elements you used for testing in that version? This information would be very helpful for resolving the issue.

latekvo avatar Nov 19 '24 13:11 latekvo

@latekvo thanks for your comment, I figured out I pasted the wrong code version, but I already updated it. I used RN's TouchOpacity, also tested it with import { TouchableOpacity } from 'react-native-gesture-handler' same error with both of them.

erie-e9 avatar Nov 20 '24 02:11 erie-e9

I have the same issue, but surprisingly it works in Android, but doesn't work in iOS.

sedrakk avatar Nov 26 '24 05:11 sedrakk

This is likely an issue with Animated.View, there isn't really a way around using it in ReanimatedSwipeable. We'll have to wait for this issue to be resolved first: https://github.com/software-mansion/react-native-reanimated/issues/6659

latekvo avatar Nov 27 '24 11:11 latekvo

if you are bumped into this issue but need solve it quickly, use the old Swipeable instead as a temporary solution. https://docs.swmansion.com/react-native-gesture-handler/docs/components/swipeable

daehyeonmun2021 avatar Dec 06 '24 03:12 daehyeonmun2021

+1

marthinhaugeterki avatar Dec 19 '24 14:12 marthinhaugeterki

The issue still persists for me with the same example:

    "react-native-gesture-handler": "2.21.2",
    "react-native-reanimated": "3.16.6",

lukachi avatar Dec 22 '24 14:12 lukachi

@lukachi Do you think I should reopen this issue?

Could you share more details, setup, devices where you're testing it, code or screenshots? I'm testing it with Hermes and new arch enabled, RN latest version and now it works fine in both platforms (iOS simulator/android real device).

erie-e9 avatar Dec 24 '24 04:12 erie-e9

+1

We are also heaving the same issue

deepktp avatar Dec 24 '24 13:12 deepktp

hi @erie-e9

package.json deps:

"dependencies": {
    "@dev-plugins/react-navigation": "^0.0.6",
    "@dev-plugins/react-query": "^0.0.6",
    "@distributedlab/tools": "^1.0.0-rc.16",
    "@gorhom/bottom-sheet": "^5.0.6",
    "@hookform/resolvers": "^3.9.0",
    "@react-native-community/hooks": "^3.0.0",
    "@react-native/js-polyfills": "^0.74.85",
    "@react-navigation/bottom-tabs": "7.0.0-rc.33",
    "@react-navigation/elements": "2.0.0-rc.25",
    "@react-navigation/native": "7.0.0-rc.20",
    "@react-navigation/native-stack": "7.0.0-rc.27",
    "@shopify/flash-list": "^1.7.2",
    "@tanstack/react-query": "^5.51.9",
    "app-icon-badge": "^0.0.15",
    "axios": "^1.7.2",
    "babel-preset-expo": "~12.0.3",
    "burnt": "^0.12.2",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "constants-browserify": "^1.0.0",
    "ethers": "^6.13.2",
    "expo": "52.0.14",
    "expo-asset": "~11.0.1",
    "expo-blur": "~14.0.1",
    "expo-build-properties": "~0.13.1",
    "expo-clipboard": "~7.0.0",
    "expo-constants": "~17.0.3",
    "expo-dev-client": "~5.0.5",
    "expo-file-system": "~18.0.4",
    "expo-font": "~13.0.1",
    "expo-haptics": "~14.0.0",
    "expo-image": "2.0.3",
    "expo-linking": "~7.0.3",
    "expo-local-authentication": "~15.0.1",
    "expo-secure-store": "~14.0.0",
    "expo-splash-screen": "^0.29.13",
    "expo-status-bar": "~2.0.0",
    "expo-system-ui": "~3.0.7",
    "expo-updates": "~0.26.9",
    "expo-web-browser": "~14.0.1",
    "i18next": "^23.11.5",
    "jsona": "^1.12.1",
    "lodash": "^4.17.21",
    "mrz": "^4.2.0",
    "nativewind": "4.1.21",
    "os-browserify": "^0.3.0",
    "path-browserify": "^1.0.1",
    "react": "18.3.1",
    "react-dom": "18.3.1",
    "react-hook-form": "^7.52.1",
    "react-i18next": "^14.1.2",
    "react-native": "0.76.3",
    "react-native-avoid-softinput": "7.0.0",
    "react-native-crypto": "^2.2.0",
    "react-native-gesture-handler": "2.21.2",
    "react-native-get-random-values": "^1.11.0",
    "react-native-linear-gradient": "^2.8.3",
    "react-native-mmkv": "3.1.0",
    "react-native-quick-base64": "^2.1.2",
    "react-native-reanimated": "3.16.6",
    "react-native-reanimated-carousel": "4.0.0-canary.22",
    "react-native-restart": "^0.0.27",
    "react-native-safe-area-context": "4.14.0",
    "react-native-screens": "4.3.0",
    "react-native-toast-message": "^2.2.0",
    "react-native-vision-camera": "4.6.3",
    "react-native-vision-camera-text-recognition": "^3.1.1",
    "react-native-worklets-core": "1.5.0",
    "react-native-zip-archive": "^7.0.1",
    "readable-stream": "^4.5.2",
    "stream-http": "^3.2.0",
    "tailwind-merge": "^2.4.0",
    "tailwind-variants": "^0.2.1",
    "tailwindcss": "^3.4.5",
    "tailwindcss-animate": "^1.0.7",
    "tinycolor2": "^1.6.0",
    "uuid": "^10.0.0",
    "yup": "^1.4.0",
    "zod": "^3.23.8",
    "zustand": "^4.5.4"
  },
  "devDependencies": {
    "@babel/core": "^7.24.6",
    "@expo/config": "~10.0.5",
    "@expo/config-plugins": "^9.0.11",
    "@expo/metro-config": "~0.19.5",
    "@expo/metro-runtime": "~4.0.0",
    "@react-native/eslint-config": "^0.76.5",
    "@react-native/eslint-plugin": "^0.76.5",
    "@tanstack/eslint-plugin-query": "^5.51.10",
    "@testing-library/jest-dom": "^6.4.6",
    "@typechain/ethers-v6": "^0.5.1",
    "@types/eslint": "^8.56.10",
    "@types/jest": "^29.5.12",
    "@types/lodash": "^4",
    "@types/path-browserify": "^1",
    "@types/react": "~18.3.12",
    "@types/react-native-get-random-values": "^1.8.2",
    "@types/readable-stream": "^4",
    "@types/tinycolor2": "^1",
    "@types/uuid": "^10",
    "@typescript-eslint/eslint-plugin": "^8.18.1",
    "@typescript-eslint/parser": "^8.18.1",
    "babel-plugin-module-resolver": "^5.0.2",
    "cross-env": "^7.0.3",
    "dotenv": "^16.4.5",
    "dotenv-cli": "^7.4.2",
    "eslint": "^8.57.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-i18n-json": "^4.0.0",
    "eslint-plugin-jest": "^28.6.0",
    "eslint-plugin-prettier": "^5.1.3",
    "eslint-plugin-simple-import-sort": "^12.1.1",
    "eslint-plugin-testing-library": "^6.2.2",
    "eslint-plugin-unused-imports": "^4.1.4",
    "expo-atlas": "^0.4.0",
    "expo-module-scripts": "^3.5.2",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-expo": "~52.0.2",
    "jest-junit": "^16.0.0",
    "np": "^10.0.7",
    "prettier": "^3.3.3",
    "prettier-plugin-tailwindcss": "^0.6.5",
    "react-native-svg": "15.9.0",
    "react-native-svg-transformer": "^1.5.0",
    "ts-jest": "^29.1.2",
    "typechain": "^8.3.2",
    "typescript": "~5.3.3"
  },

snippet:

export default function Chats() {
  const insets = useSafeAreaInsets()
  const bottomBarOffset = useBottomBarOffset()

  const { data: fakeUsers } = useLoading([], () => getFakeUser(24), {
    loadOnMount: true,
  })

  return (
    <UiScreenScrollable>
      <View
        style={{
          paddingTop: insets.top,
          paddingBottom: bottomBarOffset,
        }}
        className='flex flex-1'
      >
        <View className='flex flex-1'>
          <FlashList
            data={fakeUsers}
            estimatedItemSize={75}
            renderItem={({ item, index }) => (
              // TODO: replace by Reanimated Swipeable once it's stable and fixed left actions
              <Swipeable
                friction={2}
                enableTrackpadTwoFingerGesture
                rightThreshold={40}
                leftThreshold={40}
                renderLeftActions={() => (
                  <View className='flex h-full flex-row items-center'>
                    <TouchableOpacity
                      className='flex min-w-[60] items-center justify-center self-stretch bg-textSecondary'
                      onPress={() => console.log('delete')}
                    >
                      <UiIcon customIcon='message' size={24} className='text-baseWhite' />
                    </TouchableOpacity>
                    <TouchableOpacity
                      className='flex min-w-[60] items-center justify-center self-stretch bg-successDark'
                      onPress={() => console.log('delete')}
                    >
                      <UiIcon
                        libIcon='FontAwesome'
                        name='bookmark'
                        size={24}
                        className='text-baseWhite'
                      />
                    </TouchableOpacity>
                  </View>
                )}
                renderRightActions={() => (
                  <View className='flex h-full flex-row items-center'>
                    <TouchableOpacity
                      className='flex min-w-[60] items-center justify-center self-stretch bg-warningMain'
                      onPress={() => console.log('delete')}
                    >
                      <UiIcon
                        libIcon='FontAwesome'
                        name='volume-off'
                        size={24}
                        className='text-baseWhite'
                      />
                    </TouchableOpacity>
                    <TouchableOpacity
                      className='flex min-w-[60] items-center justify-center self-stretch bg-errorMain'
                      onPress={() => console.log('delete')}
                    >
                      <UiIcon
                        libIcon='FontAwesome'
                        name='trash'
                        size={24}
                        className='text-baseWhite'
                      />
                    </TouchableOpacity>
                    <TouchableOpacity
                      className='flex min-w-[60] items-center justify-center self-stretch bg-textSecondary'
                      onPress={() => console.log('delete')}
                    >
                      <UiIcon
                        libIcon='FontAwesome'
                        name='archive'
                        size={24}
                        className='text-baseWhite'
                      />
                    </TouchableOpacity>
                  </View>
                )}
              >
                <ListItem
                  key={index}
                  title={item.name.first}
                  text='lorem ipsum dolor sit amet concestetur'
                  updatedAt={item.registered.date}
                  avatarUri={item.picture.thumbnail}
                  newMsgAmount={index % 2 === 0 ? 1 : undefined}
                  isRead={index % 2 !== 0}
                  className={cn(
                    'bg-backgroundPrimary',
                    index !== fakeUsers.length - 1 && 'border-b border-borderPrimary',
                  )}
                />
              </Swipeable>
            )}
          />
        </View>
      </View>
    </UiScreenScrollable>
  )
}

so, the current state is - if we use deprecated Swipeable - left action works.

lukachi avatar Dec 24 '24 17:12 lukachi

Any updates on this? also experiencing this on IOS.

Alex1899 avatar Jan 02 '25 22:01 Alex1899

Having the same issue on iOS too. Any updates?

michel-lamarliere avatar Jan 09 '25 14:01 michel-lamarliere

I'm also having the same issue in ReanimatedSwipeable and it's working fine with Swipeable, Is there any update on this issue?

kapilavaiya avatar Jan 16 '25 13:01 kapilavaiya

You should be able to fix this issue by using Gesture Handler's Gestures instead of React Native's Touchables, as Touchables are currently broken with the setup you're using.

Here's an example of how you might use Gestures instead of Touchables:

Collapsed test code
import React from 'react';
import { View, Text } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';

export default function Example() {
  const leftTap = Gesture.Tap().onStart(() => console.log('tap left'));
  const rightTap = Gesture.Tap().onStart(() => console.log('tap right'));
  const centerTap = Gesture.Tap().onStart(() => console.log('tap center'));
  const innerTap = Gesture.Tap().onStart(() => console.log('tap inner'));

  return (
    <View style={{ flex: 1, marginTop: 200, backgroundColor: 'red' }}>
      <Swipeable
        onSwipeableOpen={(dir) => {
          console.log('open', dir);
        }}
        onSwipeableClose={(dir) => {
          console.log('close', dir);
        }}
        leftThreshold={50}
        rightThreshold={50}
        renderLeftActions={() => {
          return (
            <GestureDetector gesture={leftTap}>
              <View
                style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
                <Text>Left</Text>
              </View>
            </GestureDetector>
          );
        }}
        renderRightActions={() => {
          return (
            <GestureDetector gesture={rightTap}>
              <View
                style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
                <Text>Right</Text>
              </View>
            </GestureDetector>
          );
        }}>
        <GestureDetector gesture={centerTap}>
          <View
            style={{
              width: '100%',
              height: 80,
              backgroundColor: 'blue',
            }}>
            <GestureDetector gesture={innerTap}>
              <View
                style={{
                  width: 80,
                  height: 80,
                  backgroundColor: 'white',
                  alignSelf: 'center',
                }}
              />
            </GestureDetector>
          </View>
        </GestureDetector>
      </Swipeable>
    </View>
  );
}

If you really have to use the Touchables, the most likely cause of the issue (link) (which may be a related to (link)) is still yet to be resolved. Please track that issue for any potential updates.

latekvo avatar Jan 16 '25 15:01 latekvo

It's still failing with v2.22.0 I just tested with @latekvo's suggest but no success

erie-e9 avatar Jan 19 '25 00:01 erie-e9

@latekvo not work TT

bumjin1013 avatar Feb 06 '25 09:02 bumjin1013

Just to confirm. Could anyone test it with "react-native-reanimated": "4.0.0-beta.1" "react-native-gesture-handler": "2.22.1", or "2.23.1"?

erie-e9 avatar Feb 06 '25 18:02 erie-e9

@erie-e9 2.23.1 does not fix the issue (reanimated 3.16.7 though)

efstathiosntonas avatar Feb 17 '25 08:02 efstathiosntonas

Use TouchableOpacity inside a gesture handler like this: import {TouchableOpacity } from 'react-native-gesture-handler'; This will help make the container clickable during the swipe

Anurobinson avatar Mar 03 '25 07:03 Anurobinson

@Anurobinson thank you! I was able to resolve my issue with your recommendation, but in my case, I needed to use Pressable like this: import { Pressable } from "react-native-gesture-handler";

fsegovia-act avatar Mar 04 '25 04:03 fsegovia-act

I'm using: "react-native-gesture-handler": "^2.24.0", "react-native-reanimated": "^3.17.1", I'm still experiencing the issue on iOS while using import { Pressable } from "react-native-gesture-handler";

iansamz avatar Mar 05 '25 08:03 iansamz

I tested on Android but not on iOS. =(

fsegovia-act avatar Mar 05 '25 13:03 fsegovia-act

For now, I recommend using the deprecated Swipeable component. The old one works fine.

Vickers-Zhu avatar Mar 12 '25 11:03 Vickers-Zhu

+1 Why this hasn't this been fixed yet? 🥲

Yurii-Lutsyk avatar Mar 17 '25 12:03 Yurii-Lutsyk

I got working by installing this patch: https://github.com/software-mansion/react-native-gesture-handler/pull/3236#issuecomment-2782298812 + <GestureDetector /> (https://github.com/software-mansion/react-native-gesture-handler/issues/3223#issuecomment-2595962283).

const itemLeftTap = Gesture.Tap().onStart(() => {
  runOnJS(handlePress)(item);
});

return (
  <GestureDetector key={`swipe-row-left-${i}`} gesture={itemLeftTap}>
    <View>
      <Text>
        {t(item.text)
      </Text>
    </View>
  </GestureDetector>
)

PaulOskarSoe avatar May 26 '25 12:05 PaulOskarSoe

use Pressable insated of Touacbleopacity from react native gesture handler import { Pressable } from 'react-native-gesture-handler';

projectninjatech avatar May 27 '25 10:05 projectninjatech

+1 while Pressable from react-native-gesture-handler works it causes lag on Android when using an opacity wither setting it in the style or className with nativewind

enchorb avatar Jul 12 '25 01:07 enchorb

Same proble, ussing touchable or pressable from react-native-gesture-handler did not helped

maksymhcode-care avatar Aug 01 '25 12:08 maksymhcode-care