Skip to content

Commit 9d24bfb

Browse files
committed
fix: use useRef instead of useState for QR scanner lock to prevent duplicate scans
The scannerLocked state was captured in handleQrScan's closure, so rapid native barcode scanner callbacks before React re-renders would all see false, bypassing the guard. Switching to a useRef ensures the lock value is read synchronously, reliably preventing duplicate scan processing.
1 parent efc36b4 commit 9d24bfb

File tree

1 file changed

+9
-9
lines changed
  • apps/mobile/src/app/connections

1 file changed

+9
-9
lines changed

apps/mobile/src/app/connections/new.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { CameraView, useCameraPermissions } from "expo-camera";
22
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
33
import { SymbolView } from "expo-symbols";
4-
import { useCallback, useEffect, useState } from "react";
4+
import { useCallback, useEffect, useRef, useState } from "react";
55
import { Alert, Pressable, ScrollView, View } from "react-native";
66
import { useSafeAreaInsets } from "react-native-safe-area-context";
77
import { useThemeColor } from "../../lib/useThemeColor";
@@ -30,7 +30,7 @@ export default function ConnectionsNewRouteScreen() {
3030
const [isSubmitting, setIsSubmitting] = useState(false);
3131
const [showScanner, setShowScanner] = useState(params.mode === "scan_qr");
3232
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
33-
const [scannerLocked, setScannerLocked] = useState(false);
33+
const scannerLockedRef = useRef(false);
3434

3535
const textColor = useThemeColor("--color-icon");
3636
const placeholderColor = useThemeColor("--color-placeholder");
@@ -60,14 +60,14 @@ export default function ConnectionsNewRouteScreen() {
6060

6161
const openScanner = useCallback(async () => {
6262
if (cameraPermission?.granted) {
63-
setScannerLocked(false);
63+
scannerLockedRef.current = false;
6464
setShowScanner(true);
6565
return;
6666
}
6767

6868
const permission = await requestCameraPermission();
6969
if (permission.granted) {
70-
setScannerLocked(false);
70+
scannerLockedRef.current = false;
7171
setShowScanner(true);
7272
return;
7373
}
@@ -77,16 +77,16 @@ export default function ConnectionsNewRouteScreen() {
7777

7878
const closeScanner = useCallback(() => {
7979
setShowScanner(false);
80-
setScannerLocked(false);
80+
scannerLockedRef.current = false;
8181
}, []);
8282

8383
const handleQrScan = useCallback(
8484
({ data }: { readonly data: string }) => {
85-
if (scannerLocked) {
85+
if (scannerLockedRef.current) {
8686
return;
8787
}
8888

89-
setScannerLocked(true);
89+
scannerLockedRef.current = true;
9090

9191
try {
9292
const pairingUrl = extractPairingUrlFromQrPayload(data);
@@ -102,11 +102,11 @@ export default function ConnectionsNewRouteScreen() {
102102
);
103103
} finally {
104104
setTimeout(() => {
105-
setScannerLocked(false);
105+
scannerLockedRef.current = false;
106106
}, 600);
107107
}
108108
},
109-
[onChangeConnectionPairingUrl, scannerLocked],
109+
[onChangeConnectionPairingUrl],
110110
);
111111

112112
const handleSubmit = useCallback(async () => {

0 commit comments

Comments
 (0)