fix: SlideToConfirm stale closure — live refs for disabled + onConfirm; horizontal-only drag on iOS

This commit is contained in:
Nemo
2026-03-25 05:43:44 +08:00
parent 831dd6d5a9
commit 295c32e6e7
2 changed files with 15 additions and 5 deletions

View File

@@ -9,7 +9,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as Location from 'expo-location'; import * as Location from 'expo-location';
import { api } from '../../../services/api'; import { api } from '../../../services/api';
import { useAuthStore } from '../../../stores/authStore'; import { useAuthStore } from '../../../stores/authStore';
import { SlideToConfirm } from '../../../components/SlideToConfirm';
// ─── Constants ──────────────────────────────────────────────────────────────── // ─── Constants ────────────────────────────────────────────────────────────────
const STATUS_FLOW = ['OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED'] as const; const STATUS_FLOW = ['OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED'] as const;

View File

@@ -1,6 +1,9 @@
/** /**
* SlideToConfirm — pure RN, no RNGH, no Reanimated. * SlideToConfirm — pure RN, no RNGH, no Reanimated.
* PanResponder on handle + Animated(useNativeDriver:false). * PanResponder on handle + Animated(useNativeDriver:false).
*
* Fix: live refs break stale closures — disabledRef + onConfirmRef
* updated on every render so PanResponder always reads current values.
*/ */
import { useRef } from 'react'; import { useRef } from 'react';
import { import {
@@ -28,17 +31,25 @@ export function SlideToConfirm({ onConfirm, label = 'Slide to confirm', disabled
const confirmed = useRef(false); const confirmed = useRef(false);
const translateX = useRef(new Animated.Value(0)).current; const translateX = useRef(new Animated.Value(0)).current;
// ── Live refs: break stale closures inside PanResponder ──────────────────
const disabledRef = useRef(disabled);
disabledRef.current = disabled; // updated on every render
const onConfirmRef = useRef(onConfirm);
onConfirmRef.current = onConfirm; // updated on every render
const panResponder = useRef( const panResponder = useRef(
PanResponder.create({ PanResponder.create({
onStartShouldSetPanResponder: () => !disabled, onStartShouldSetPanResponder: () => !disabledRef.current,
onMoveShouldSetPanResponder: () => !disabled, // Horizontal-dominance check prevents ScrollView stealing vertical-ish drags on iOS
onMoveShouldSetPanResponder: (_, gs) =>
!disabledRef.current && Math.abs(gs.dx) > Math.abs(gs.dy) * 2,
onPanResponderGrant: () => { onPanResponderGrant: () => {
confirmed.current = false; confirmed.current = false;
(translateX as any).setOffset((translateX as any)._value); (translateX as any).setOffset((translateX as any)._value);
translateX.setValue(0); translateX.setValue(0);
}, },
onPanResponderMove: (_, gs) => { onPanResponderMove: (_, gs) => {
const maxX = Math.max(trackWidth.current - HANDLE_SIZE - 6, 1); const maxX = Math.max(trackWidth.current - HANDLE_SIZE - 6, 1);
const clamped = Math.min(Math.max(gs.dx, 0), maxX); const clamped = Math.min(Math.max(gs.dx, 0), maxX);
translateX.setValue(clamped - (translateX as any)._offset); translateX.setValue(clamped - (translateX as any)._offset);
@@ -51,7 +62,7 @@ export function SlideToConfirm({ onConfirm, label = 'Slide to confirm', disabled
damping: 18, damping: 18,
stiffness: 220, stiffness: 220,
}).start(); }).start();
onConfirm(); onConfirmRef.current(); // always calls latest onConfirm
} }
}, },
onPanResponderRelease: () => { onPanResponderRelease: () => {