Gesten mit Gesture Handler in React Native: Swipe-to-Delete
Gesten mit Gesture Handler: Swipe-to-Delete
~17 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
React Natives eingebautes PanResponder-System zur Gestenerkennung läuft vollständig auf dem JS-Thread – bei komplexeren Gesten (mehrere gleichzeitige Touches, Swipe-Schwellenwerte) kann das zu spürbarem Ruckeln führen. react-native-gesture-handler erkennt Gesten stattdessen NATIV, kombiniert mit Reanimated aus dem letzten Kapitel für eine vollständig JS-Thread-unabhängige Swipe-to-Delete-Funktion in unserem Warenkorb.
Installation
npx expo install react-native-gesture-handler// App.js, ganz oben, VOR allen anderen Imports:
import 'react-native-gesture-handler';Achtung: Der Import 'react-native-gesture-handler' MUSS die ALLERERSTE Zeile in App.js sein – GENAU dieselbe Regel wie beim Drawer-Navigator aus der "React Native Referenz"-Serie (Thema "Drawer Navigation"), da beide auf demselben nativen Setup aufbauen.
Zuerst: einen echten CartScreen anlegen
Bisher zeigt unsere App den Warenkorb-INHALT nirgends an, nur das Badge aus Kapitel 1/2. Wir bauen jetzt einen vollständigen CartScreen mit Swipe-to-Delete:
import { View, Text, StyleSheet } from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
runOnJS,
} from 'react-native-reanimated';
import { useCartStore } from '../store/cartStore';
const SWIPE_THRESHOLD = -100;
function SwipeableCartItem({ item, onRemove }) {
const translateX = useSharedValue(0);
const panGesture = Gesture.Pan()
.onUpdate((event) => {
translateX.value = Math.min(0, event.translationX);
})
.onEnd(() => {
if (translateX.value < SWIPE_THRESHOLD) {
translateX.value = withTiming(-500, {}, () => {
runOnJS(onRemove)(item.sku);
});
} else {
translateX.value = withTiming(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.item, animatedStyle]}>
<Text style={styles.itemName}>{item.name}</Text>
<Text style={styles.itemPrice}>${item.price.toFixed(2)}</Text>
</Animated.View>
</GestureDetector>
);
}
function CartScreen() {
const cart = useCartStore((state) => state.cart);
const removeProduct = useCartStore((state) => state.removeProduct);
if (cart.length === 0) {
return <Text style={styles.emptyText}>Ihr Warenkorb ist leer.</Text>;
}
return (
<View style={styles.container}>
<Text style={styles.hint}>← Nach links wischen zum Entfernen</Text>
{cart.map((item) => (
<SwipeableCartItem key={item.sku} item={item} onRemove={removeProduct} />
))}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 16, paddingHorizontal: 16 },
hint: { textAlign: 'center', color: '#9ca3af', marginBottom: 12, fontSize: 12 },
item: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: '#f9fafb',
padding: 14,
borderRadius: 8,
marginBottom: 8,
},
itemName: { fontSize: 16 },
itemPrice: { fontSize: 16, fontWeight: 'bold' },
emptyText: { textAlign: 'center', marginTop: 40, color: '#9ca3af' },
});
export default CartScreen;cartStore.js um removeProduct ergänzen (falls noch nicht vorhanden)
Kapitel 2 zeigte cartStore.js nur mit addProduct – für CartScreen ergänzen wir removeProduct analog zu favoritesSlices Muster aus Kapitel 3:
// In store/cartStore.js, innerhalb des persist()-Objekts, nach addProduct ergänzen:
removeProduct(sku) {
set({ cart: get().cart.filter((item) => item.sku !== sku) });
},Die neuen Bausteine im Detail
Gesture.Pan()erzeugt einen Gesten-Erkenner für Ziehbewegungen –.onUpdatefeuert bei JEDER Bewegung (mit der aktuellenevent.translationX-Distanz seit Gestenbeginn),.onEndbeim Loslassen.Math.min(0, event.translationX)begrenzt die Bewegung auf NEGATIVE Werte (nur nach links) – ein Wisch nach rechts hat keinen Effekt.runOnJS(onRemove)(item.sku)ist entscheidend:onUpdate/onEnd-Callbacks sind WORKLETS, laufen auf dem UI-Thread – der eigentlicheremoveProduct-Aufruf (der den Redux-artigen Zustand-Store aktualisiert, JS-Thread-Code) MUSS explizit mitrunOnJS()zurück auf den JS-Thread geschickt werden. Ein direkter Aufruf vononRemove(item.sku)innerhalb des Worklets würde einen Laufzeitfehler auslösen.GestureDetectorstatt des älterenPanGestureHandler-Komponenten-Musters – die neue, empfohlene API-Generation von Gesture Handler, aufbauend auf demselben Worklet-Konzept wie Reanimated.
Achtung: runOnJS ist eine der häufigsten Stolperfallen beim Kombinieren von Reanimated und Gesture Handler: JEDER Aufruf "normaler" JavaScript-Funktionen (State-Updates, Store-Dispatches, Navigation) INNERHALB eines Worklets muss durch runOnJS() gekapselt werden. Nur reine Berechnungen und Zuweisungen an .value dürfen DIREKT im Worklet passieren.
CartScreen in die Navigation einbinden
Ergänzen Sie App.js um einen weiteren <Stack.Screen>-Eintrag für CartScreen (analog zu FavoritesScreen aus Kapitel 3) und verlinken Sie das Warenkorb-Badge aus ProductListScreen (Kapitel 1) darauf, statt es nur anzuzeigen.
Tipp: Testen Sie auf einem ECHTEN Gerät oder im Simulator mit Maus-Drag – Sie werden spüren, dass die Wisch-Bewegung dem Finger/Cursor OHNE Verzögerung folgt, selbst wenn Sie testweise den JS-Thread künstlich blockieren (siehe die filterItemsSlowly-Technik aus "React für Profis" Kapitel 33) – ein direkter, spürbarer Beweis für das, was JSI-basierte Worklets ermöglichen.