I’m using the react-native-gesture-handler ReanimatedSwipeable and I’m stuck on a weird problem, more I add OrderItems in my list more it is difficult to swipe left to delete the item, for example as I add new items it becomes more difficult to swipe to the left, it almost feels blocking like. When I have let’s say three items in my list (three OrderItems), it works perfectly I ca n drag the row left and right as long as I hold my finger on it and then when there’s more items it’s a chore to even make it move a bit to the left.
Here’s my OrderItem component, maybe I didn’t implement the react-native-gesture-handler correctly? I really don’t know
import React, {useRef} from 'react';
import {ImageBackground, View} from 'react-native';
import {Product} from '../../types';
import ProductName from '../product/ProductName';
import ProductPrice from '../product/ProductPrice';
import AddQuantityButton from "../add-quantity-button/AddQuantityButton";
import Swipeable, {SwipeableMethods} from 'react-native-gesture-handler/ReanimatedSwipeable';
import {GestureHandlerRootView, RectButton} from "react-native-gesture-handler";
import {Extrapolation, interpolate, SharedValue, useAnimatedStyle} from "react-native-reanimated";
import {Feather} from "@expo/vector-icons";
import {useAppDispatch} from "../../hooks";
import {removeCompletelyFromCart} from "../../store/slices/cartSlice";
type Props = {
item: Product;
lastElement?: boolean;
};
interface LeftActionsProps {
dragX: SharedValue<number>;
swipeableRef: React.RefObject<SwipeableMethods>;
}
const renderRightActions = (
_progress: any,
translation: SharedValue<number>,
swipeableRef: React.RefObject<SwipeableMethods>
) => <RightAction dragX={translation} swipeableRef={swipeableRef}/>;
const RightAction = ({dragX, swipeableRef}: LeftActionsProps) => {
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: interpolate(
dragX.value,
[0, 50, 100, 101],
[-20, 0, 0, 1],
Extrapolation.CLAMP
),
},
],
}));
return (
<RectButton
style={{
flex: 1,
backgroundColor: 'red',
alignItems: "flex-end",
justifyContent: "center",
width: 50,
height: 100,
}}
onPress={() => swipeableRef.current!.close()}>
<View style={{padding: 32}}>
<Feather name="trash" size={20} color="white"/>
</View>
</RectButton>
);
};
const OrderItem: React.FC<Props> = ({item, lastElement}): JSX.Element => {
const marginBottom = lastElement ? 30 : 14;
const dispatch = useAppDispatch();
const deleteItem = () => {
dispatch(removeCompletelyFromCart(item));
}
const renderImage = () => {
return (
<View style={{justifyContent: "center"}}>
<ImageBackground
source={{uri: item.image?.url}}
style={{width: 92, height: 92}}
resizeMode='contain'
/>
</View>
);
};
const renderInfo = () => {
return (
<View style={{
flex: 1,
paddingRight: 0,
justifyContent: 'space-between',
}}>
<View style={{
marginHorizontal: 10,
}}>
<ProductName item={item} version={1} style={{marginBottom: 3}}/>
<ProductPrice item={item} version={1} containerStyle={{marginBottom: 'auto'}}/>
</View>
<View style={{justifyContent: "center", marginLeft: 20}}>
<AddQuantityButton
key={item.id}
item={item}
containerStyle={{bottom: '0%'}}
openExpandedView={true}
/>
</View>
</View>
);
};
// const renderRightActions = (progress: Animated.AnimatedInterpolation<number>, dragX: Animated.AnimatedInterpolation<number>) => {
// const trans = dragX.interpolate({
// inputRange: [-100, 0],
// outputRange: [0, 100],
// extrapolate: 'clamp',
// });
// return (
// <View style={{width: 100, justifyContent: 'center', alignItems: 'center'}}>
// <Animated.View style={{transform: [{translateX: trans}]}}>
// <View style={{
// backgroundColor: 'red',
// justifyContent: 'center',
// alignItems: 'center',
// width: 100,
// height: '100%',
// }}>
// <Animated.Text
// style={{
// color: 'white',
// fontWeight: '600',
// transform: [{
// scale: progress.interpolate({
// inputRange: [0, 1],
// outputRange: [0.5, 1],
// }),
// }],
// }}
// >
// Delete
// </Animated.Text>
// </View>
// </Animated.View>
// </View>
// );
// };
const swipeableRow = useRef<SwipeableMethods>(null);
return (
<GestureHandlerRootView>
<Swipeable
renderRightActions={(_, progress) =>
renderRightActions(_, progress, swipeableRow)
}
onSwipeableOpen={deleteItem}
>
<View>
<View style={{
flexDirection: 'row',
width: '100%',
height: 100,
marginBottom: marginBottom,
alignItems: 'center',
backgroundColor: 'white',
}}>
{renderImage()}
{renderInfo()}
</View>
{!lastElement && (
<View style={{
borderBottomWidth: 0.2,
borderColor: 'lightgrey',
width: '80%',
alignSelf: 'center',
}}/>
)}
</View>
</Swipeable>
</GestureHandlerRootView>
);
};
export default OrderItem;
Here’s my order page where I have a list of orderitems for your curiosity or if it can help in anyway:
import React from 'react';
import {ScrollView, Text, TouchableOpacity, View} from 'react-native';
import {text} from '../../text';
import {svg} from '../../assets/svg';
import {useAppDispatch, useAppNavigation, useAppSelector} from '../../hooks';
import {components} from '../../components';
import {tabs, theme} from '../../constants';
import {MAX_ORDER_QUANTITY} from "../../constants/constants";
import {showMessage} from "react-native-flash-message";
import {statusBarHeight} from "../../utils";
import {Feather} from "@expo/vector-icons";
import {setScreen} from "../../store/slices/tabSlice";
const Order = () => {
const navigation = useAppNavigation();
const dispatch = useAppDispatch();
const totalPrice = useAppSelector((state) => state.cartSlice.totalPrice) ?? 0;
const totalWeight = useAppSelector((state) => state.cartSlice.totalQuantityKg);
const currencyCode = useAppSelector((state) => state.cartSlice.currencyCode) ?? "DZD";
const cart = useAppSelector((state) => state.cartSlice.list);
const user = useAppSelector((state) => state.appState.user);
const isProceedToCheckOutEnabled = totalWeight >= MAX_ORDER_QUANTITY;
const height = statusBarHeight();
const renderStatusBar = (): JSX.Element => {
return (
<components.StatusBar
backgroundColor={theme.colors.transparent}
barStyle='dark-content'
/>
);
};
const renderHeader = (): JSX.Element => {
return (
<components.Header
title='Basket'
/>
);
};
const renderTabBar = (): JSX.Element => {
return (
<components.TabBar>
{tabs.map((item, index) => {
return <components.TabBarItem item={item} key={index}/>;
})}
</components.TabBar>
);
};
const delivery = 0;
const renderProductsInCart = () => {
return (
<View style={{paddingHorizontal: 12}}>
{cart.map((item, index, array) => {
const lastElement = index === array.length - 1;
return (
<components.OrderItem
key={index}
item={item.product}
lastElement={lastElement}
/>
);
})}
</View>
);
};
const navigateToHomeTab = () => {
dispatch(setScreen('Home'));
navigation.navigate('TabNavigator');
};
const renderCircularButton = () => {
return (
<TouchableOpacity style={styles.container} onPress={navigateToHomeTab}>
<View style={styles.button}>
<Feather name="plus" size={20} color="black"/>
</View>
</TouchableOpacity>
);
};
const renderEmptyCart = () => {
return (
<View style={{justifyContent: "center", alignItems: "center"}}>
<svg.ShoppingBagSvg/>
<text.H2 style={{marginTop: 30, marginBottom: 14, fontSize: 18, color: theme.colors.textColor}}>
Your basket is empty!
</text.H2>
<View style={{flexDirection: 'column', paddingHorizontal: 18, alignItems: "center"}}>
<Text style={{fontSize: 16, color: theme.colors.textColor, marginRight: 10, textAlign: "center", paddingBottom: 12, lineHeight: 28}}>Start adding fruits and vegetables by clicking on</Text>
{renderCircularButton()}
</View>
</View>
);
};
const renderTotal = () => {
return (
<components.Container
containerStyle={{
marginHorizontal: 20,
}}
>
<components.ContainerItem
title='Subtotal'
price={`${totalPrice} ${currencyCode}`}
titleStyle={{
...theme.fonts.H5,
color: theme.colors.textColor,
fontSize: 16
}}
priceStyle={{
...theme.fonts.textStyle_14,
color: theme.colors.textColor,
fontSize: 16
}}
/>
<components.ContainerItem
title='Delivery'
price={`${delivery} DZD`}
titleStyle={{
fontSize: 16
}}
containerStyle={{
marginBottom: 14,
}}
priceStyle={{
fontSize: 16
}}
/>
<components.ContainerLine/>
<components.ContainerItem
title='Total'
price={`${totalPrice} ${currencyCode}`}
containerStyle={{
marginTop: 8,
marginBottom: 0,
}}
titleStyle={{
...theme.fonts.H4,
color: theme.colors.textColor,
fontSize: 16
}}
priceStyle={{
...theme.fonts.H4,
color: theme.colors.textColor,
fontSize: 16
}}
/>
</components.Container>
);
};
const renderContent = (): JSX.Element => {
return (
<ScrollView
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 20,
paddingBottom: 138,
paddingHorizontal: cart.length === 0 ? 20 : 0,
justifyContent: cart.length === 0 ? 'center' : 'flex-start',
}}
>
{cart.length === 0 ? renderEmptyCart() : renderProductsInCart()}
{cart.length !== 0 && renderTotal()}
</ScrollView>
);
};
const onPressProceedToCheckoutButton = () => {
if (!user) {
navigation.navigate('SignIn');
} else if (cart.length !== 0) {
navigation.navigate('Checkout');
}
}
const renderProceedToCheckoutButton = () => {
if (cart.length <= 0) {
return;
}
return (
<View style={{
position: 'absolute',
padding: 20,
bottom: 58,
}}>
<components.Button
title={'Proceed to checkout'}
onPress={() => onPressProceedToCheckoutButton()}
disabled={!isProceedToCheckOutEnabled}
onPressDisabled={() => {
showMessage({
message: 'Oops!',
description: `Your basket needs to be ${MAX_ORDER_QUANTITY} kg to checkout`,
type: 'danger',
icon: 'danger',
position: 'top',
duration: 2000,
hideOnPress: true,
style: {
zIndex: 9999,
marginTop: height,
}
});
}}
/>
</View>
);
};
return (
<components.SmartView>
{renderStatusBar()}
{renderHeader()}
<View style={{flex: 1}}>
{renderContent()}
{renderProceedToCheckoutButton()}
</View>
{renderTabBar()}
</components.SmartView>
);
};
const styles = {
container: {
width: 40,
height: 40,
backgroundColor: 'white',
borderRadius: 20,
shadowColor: "#000",
shadowOffset: {width: 0, height: 1},
shadowOpacity: 0.22,
shadowRadius: 2.22,
elevation: 4,
},
button: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
};
export default Order;