react-native-gesture-handler
react-native-gesture-handler copied to clipboard
Swipeable renderLeftActions button not firing
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
- Implement Swipeable inside a GestureHandlerRootView, with two levels of TouchableOpacity children.
- Swipe on the Swipeable. Both right and left TouchableOpacity can be displayed.
- Once the button is displayed, try to trigger onPress action.
- 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
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?
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>
);
}
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 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.
I have the same issue, but surprisingly it works in Android, but doesn't work in iOS.
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
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
+1
The issue still persists for me with the same example:
"react-native-gesture-handler": "2.21.2",
"react-native-reanimated": "3.16.6",
@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).
+1
We are also heaving the same issue
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.
Any updates on this? also experiencing this on IOS.
Having the same issue on iOS too. Any updates?
I'm also having the same issue in ReanimatedSwipeable and it's working fine with Swipeable, Is there any update on this issue?
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.
It's still failing with v2.22.0 I just tested with @latekvo's suggest but no success
@latekvo not work TT
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 2.23.1 does not fix the issue (reanimated 3.16.7 though)
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 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";
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";
I tested on Android but not on iOS. =(
For now, I recommend using the deprecated Swipeable component. The old one works fine.
+1 Why this hasn't this been fixed yet? 🥲
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>
)
use Pressable insated of Touacbleopacity from react native gesture handler import { Pressable } from 'react-native-gesture-handler';
+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
Same proble, ussing touchable or pressable from react-native-gesture-handler did not helped