Can't swipe right in tinder example in docs
See the following tinder example of the latest docs:
https://rn-carousel.dev/Examples/custom-animations/tinder
Can't really swipe right. only left.
Hey @jony89! I'm here to help you with any bugs, questions, or contributions you have in mind. Let's tackle this together!
To enable swiping right in the Tinder example from the react-native-reanimated-carousel documentation, you need to modify the fixedDirection property in the Carousel component. The current setup uses fixedDirection="negative", which restricts swiping to the left. To allow swiping right, you should remove the fixedDirection property or set it to null. Here's the relevant part of the code you need to adjust:
<Carousel
loop={false}
style={{
width: PAGE_WIDTH,
height: PAGE_HEIGHT,
justifyContent: "center",
alignItems: "center",
}}
defaultIndex={0}
vertical={false}
width={PAGE_WIDTH}
height={PAGE_HEIGHT}
data={data}
onConfigurePanGesture={(g) => {
g.onChange((e) => {
"worklet";
directionAnimVal.value = Math.sign(e.translationX);
});
}}
// Remove or set fixedDirection to null to enable swiping right
fixedDirection={null}
renderItem={({ index, item }) => <Item key={index} img={item} />}
customAnimation={animationStyle}
windowSize={5}
/>
This change will allow the carousel to support swiping in both directions [1].
To continue talking to Dosu, mention @dosu.
Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Other
Nope changing the fixedDirection to undefined / null does not help
@dosu
@dosu the snap does not work when swiping right
To address the issue with swiping right and the snapping not working, you might want to check the onConfigurePanGesture function. Ensure that it is not disabling the gesture or affecting the swipe direction. For example, make sure that the gesture is enabled:
onConfigurePanGesture={(g) => {
"worklet";
g.enabled(true); // Ensure the gesture is enabled
g.onChange((e) => {
directionAnimVal.value = Math.sign(e.translationX);
});
}}
Additionally, verify that the snapEnabled property is set to true, as it controls the snapping behavior:
<Carousel
...
snapEnabled={true} // Ensure snapping is enabled
...
/>
These adjustments should help in enabling the swipe right functionality and ensure that snapping works as expected [1][2].
To continue talking to Dosu, mention @dosu.
Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Other
@dosu this does not change a thing
Ensure that the fixedDirection property is not set, as it can restrict swipe directions. Additionally, check the pagingEnabled and snapEnabled properties. Both should be set to true to ensure proper snapping behavior:
<Carousel
...
pagingEnabled={true} // Ensure paging is enabled
snapEnabled={true} // Ensure snapping is enabled
...
/>
If the issue persists, verify that there are no other configurations or custom animations affecting the swipe direction or snapping behavior. Also, ensure that the onConfigurePanGesture function is correctly enabling gestures and not interfering with the swipe logic.
To continue talking to Dosu, mention @dosu.
Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Other
me too I just copy and parse all code from example and try to do what bot say still happended
Hey, I've released the v4. Could you try with the latest version and let me know what happened?
I am having the same problem. Thank you for all your maintenance. It would be helpful if you could provide us with a solution.
My code is as follows.
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import { Extrapolation, interpolate, SharedValue, useSharedValue } from 'react-native-reanimated';
import Carousel, { ICarouselInstance, TAnimationStyle } from 'react-native-reanimated-carousel';
import { Layout } from '@/constants/Layout';
import { Spot } from '@/types/Spot';
import { CarouselTinderItem } from './CarouselTinderItem';
type Props = {
carouselRef: React.RefObject<ICarouselInstance>;
progress: SharedValue<number>;
data: Spot[];
onSwipe: (index: number, isLike: boolean, item: Spot) => void;
};
export const CarouselTinder = (props: Props) => {
const { carouselRef, progress, data, onSwipe } = props;
const directionAnimVal = useSharedValue(0);
const animationStyle: TAnimationStyle = React.useCallback(
(value: number) => {
'worklet';
const translateY = interpolate(value, [0, 1], [0, -18]);
const translateX = interpolate(value, [-1, 0], [PAGE_WIDTH, 0], Extrapolation.CLAMP) * directionAnimVal.value;
const rotateZ = interpolate(value, [-1, 0], [15, 0], Extrapolation.CLAMP) * directionAnimVal.value;
const zIndex = interpolate(
value,
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4].map((v) => (data.length - v) * 10),
Extrapolation.CLAMP,
);
const scale = interpolate(value, [0, 1], [1, 0.95]);
const opacity = interpolate(value, [-1, -0.8, 0, 1], [0, 0.9, 1, 0.85], Extrapolation.EXTEND);
return {
transform: [{ translateY }, { translateX }, { rotateZ: `${rotateZ}deg` }, { scale }],
zIndex,
opacity,
};
},
[data.length, directionAnimVal.value],
);
return (
<View>
<Carousel
ref={carouselRef}
loop={false}
style={styles.container}
defaultIndex={0}
vertical={false}
width={PAGE_WIDTH}
height={PAGE_HEIGHT}
data={[...data, undefined]}
pagingEnabled={true}
snapEnabled={true}
onConfigurePanGesture={(g) => {
g.onChange((e) => {
'worklet';
directionAnimVal.value = Math.sign(e.translationX);
});
}}
// fixedDirection="negative"
renderItem={({ index, item }) => (item ? <CarouselTinderItem key={index} img={item.image.uri} /> : <></>)}
customAnimation={animationStyle}
windowSize={5}
onProgressChange={progress}
/>
</View>
);
};
const PAGE_WIDTH = Layout.window.width;
const PAGE_HEIGHT = Layout.window.height;
const styles = StyleSheet.create({
container: {
width: PAGE_WIDTH,
height: PAGE_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
},
});
remove fixedDirection
Removing fixedDirection does not allow swiping to the right.
https://github.com/user-attachments/assets/3d3134d9-5b1f-4405-a241-46dc2148bf13
fixedDirection="negative"
If fixedDirection=“negative”, you can swipe to the right, but it does not move to the next card.
https://github.com/user-attachments/assets/81d3a21b-4980-4d4b-b607-2f50f7e66578
After giving this a lot of my time, my conclusion was that carousel-like component does not fit to the tinder like style for the main reason that tinder requires cards "stack" as opposed to carousel.
For example if you would move the card on the top very far outside the screen the second card will start moving as well under the same gesture.
My solution was eventually to implement my own cards stack with react animation style. actually it was not a lot of work.
I've used the mix of the following tutorials:
https://www.youtube.com/watch?v=m3KqkvqfI48&ab_channel=WilliamCandillon https://github.com/Rakha112/react-native-animation/tree/main/season1/src/29-React-Native-Card-Swipe
I have the same issue as @nakapon9517.
same issue +1 @dohooo
+1
also running into weird opacity / zindex issues when i just run the example out of the box
can anybody provide a working solution ? @dohooo
temporary solution
downgrade the version to 4.0.0-canary.18
Try my code I use AI to help me create this it not perfect for look like stack card,
you can change line const translateY = index * 0 from 0 to other like 10 for make look like stack (this is tricky because animation seem little flash that why I use 0 for smooth)
this is seem ok for me I try 100 card it working well
import React, { useState } from 'react'
import { View, StyleSheet, Dimensions, Image, Text } from 'react-native'
import { GestureHandlerRootView, PanGestureHandler } from 'react-native-gesture-handler'
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
withSpring,
interpolate,
Extrapolate,
runOnJS,
} from 'react-native-reanimated'
// Sample data - replace with your actual data source if needed
const data: { id: number, uri: string }[] = Array(1000) // Increased to 1000
.fill({})
.map((_, index) => ({
id: index,
uri: `https://picsum.photos/id/${index + 1}/400/400`, // Using picsum.photos for example
}))
const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
const cardWidth = screenWidth * 0.9
const cardHeight = screenHeight * 0.6
const swipeThreshold = screenWidth * 0.4 // Distance to trigger a swipe
const swipeVelocityThreshold = 500 // Velocity threshold to trigger a swipe
const TinderStack = () => {
const [currentIndex, setCurrentIndex] = useState(0)
const [cardStack, setCardStack] = useState(() => data.slice(0, 3).reverse()) // Start with first 3 cards, reversed for stacking
const [isAnimatingOut, setIsAnimatingOut] = useState(false) // <-- เพิ่ม state นี้
const translateX = useSharedValue(0)
const translateY = useSharedValue(0)
const rotate = useSharedValue(0)
// Function to update state on the JS thread after animation
const handleSwipeComplete = (swipedRight: boolean) => {
setTimeout(() => {
console.log(`Swiped ${swipedRight ? 'Right' : 'Left'} on card ${cardStack[cardStack.length - 1]?.id}`)
// Reset animations for the new top card *immediately*
// This runs on the JS thread via runOnJS, but before state updates
translateX.value = 0
translateY.value = 0
rotate.value = 0
// Update current index and card stack
setCurrentIndex((prevIndex) => {
const nextIndex = prevIndex + 1
setCardStack((prevStack) => {
const newStack = prevStack.slice(0, -1) // Remove the swiped card
// Simplified check and calculation for the next card index
const nextCardIndexInData = nextIndex + newStack.length
if (nextCardIndexInData < data.length) { // Check if there's a next card in the original data
newStack.unshift(data[nextCardIndexInData]) // Add the next card to the beginning of the stack array (bottom visually)
}
return newStack
})
return nextIndex
})
// Re-enable gestures AFTER state update and reset are complete
setIsAnimatingOut(false) // <-- This still happens last
}, 250)
}
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx: any) => {
ctx.startX = translateX.value
ctx.startY = translateY.value
},
onActive: (event, ctx: any) => {
translateX.value = ctx.startX + event.translationX
translateY.value = ctx.startY + event.translationY
// Calculate rotation based on horizontal movement
rotate.value = interpolate(
translateX.value,
[-screenWidth / 2, screenWidth / 2],
[-15, 15], // Rotate up to 15 degrees
Extrapolate.CLAMP
)
},
onEnd: (event) => {
const currentX = translateX.value
const velocityX = event.velocityX
const shouldSwipe =
Math.abs(currentX) > swipeThreshold ||
Math.abs(velocityX) > swipeVelocityThreshold
if (shouldSwipe) {
const swipeDirection = Math.abs(currentX) > swipeThreshold
? Math.sign(currentX)
: Math.sign(velocityX)
// Disable gestures immediately
runOnJS(setIsAnimatingOut)(true)
// Start the animations *without* the callback on translateX
translateX.value = withSpring(
swipeDirection * screenWidth * 1.5,
{ damping: 100, stiffness: 100 }
// Remove the callback from here
)
rotate.value = withSpring(
swipeDirection * 45,
{ damping: 100, stiffness: 100 }
)
// Run the state update immediately after starting the animation
runOnJS(handleSwipeComplete)(swipeDirection > 0)
} else {
// Return to center
translateX.value = withSpring(0)
translateY.value = withSpring(0)
rotate.value = withSpring(0)
}
},
})
const animatedCardStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
{ rotateZ: `${rotate.value}deg` },
],
}
})
// Style for cards underneath the top card
const getCardStyle = (index: number) => {
// const scale = 1 - index * 0.05 // Remove scaling
const translateY = index * 0 // Slightly lower position for stacking effect
return {
transform: [{ translateY }], // Only apply vertical translation
// opacity: 1 - index * 0.1, // Remove opacity change
}
}
if (!cardStack.length) {
return (
<View style={styles.container}>
<Text>No more cards!</Text>
</View>
)
}
return (
// Important: Wrap your app or screen in GestureHandlerRootView
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
{cardStack.map((item, index) => {
const isTopCard = index === cardStack.length - 1
const cardStyle = isTopCard ? animatedCardStyle : getCardStyle(cardStack.length - 1 - index) // Apply base style to non-top cards
if (isTopCard) {
return (
<PanGestureHandler
key={item.id}
onGestureEvent={gestureHandler}
enabled={!isAnimatingOut} // <-- เพิ่ม prop enabled ที่นี่
>
<Animated.View style={[styles.card, styles.animatedCard, cardStyle]}>
<Image source={{ uri: item.uri }} style={styles.image} />
</Animated.View>
</PanGestureHandler>
)
} else {
// Render non-interactive cards underneath
return (
<Animated.View key={item.id} style={[styles.card, cardStyle]}>
<Image source={{ uri: item.uri }} style={styles.image} />
</Animated.View>
)
}
})}
</View>
</GestureHandlerRootView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0', // Background color for the screen
},
card: {
width: cardWidth,
height: cardHeight,
borderRadius: 15,
backgroundColor: 'white',
position: 'absolute', // Important for stacking
overflow: 'hidden', // Ensure image stays within bounds
// Shadow for iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
// Elevation for Android
elevation: 5,
},
animatedCard: {
// Top card specific styles if needed, zIndex might be useful if issues arise
zIndex: 100, // Ensure the draggable card is on top
},
image: {
width: '100%',
height: '100%',
borderRadius: 15, // Match card's border radius
},
})
export default TinderStack
Hi, @jony89. I'm Dosu, and I'm helping the react-native-reanimated-carousel team manage their backlog. I'm marking this issue as stale.
Issue Summary:
- The issue involves a problem with the Tinder example where users are unable to swipe right.
- Suggestions to modify properties like
fixedDirection,pagingEnabled, andsnapEnableddid not resolve the issue. - Multiple users, including zoom2009 and nakapon9517, reported similar problems.
- Dohooo suggested trying the latest version (v4), but issues persisted.
- You concluded that the component might not be suitable for a Tinder-like card stack and implemented a custom solution.
Next Steps:
- Please let me know if this issue is still relevant to the latest version of the react-native-reanimated-carousel repository. If so, you can keep the discussion open by commenting on the issue.
- Otherwise, this issue will be automatically closed in 7 days.
Thank you for your understanding and contribution!