Common Patterns
Practical examples for the most common scanner use cases.
Scan once, show result, scan again
A very common pattern: scan a code, show the result to the user, wait for them to confirm, then let them scan another.
import React, { useRef, useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
import { Scanner } from 'react-native-scanner-pro';
export default function ScanOnceScreen() {
const scannerRef = useRef(null);
const [result, setResult] = useState(null);
function handleScan(scanned) {
setResult(scanned.data);
// Camera is paused (because enableFreezeFrame is on)
}
function handleNext() {
setResult(null);
scannerRef.current?.resumeScanning();
}
return (
<View style={{ flex: 1 }}>
<Scanner
ref={scannerRef}
style={StyleSheet.absoluteFill}
enableFreezeFrame={true}
onCodeScanned={handleScan}
/>
{result && (
<View style={styles.card}>
<Text style={styles.label}>Scanned:</Text>
<Text style={styles.value}>{result}</Text>
<Pressable style={styles.button} onPress={handleNext}>
<Text style={styles.buttonText}>Scan another</Text>
</Pressable>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
card: {
position: 'absolute',
bottom: 40,
left: 20,
right: 20,
backgroundColor: '#fff',
borderRadius: 16,
padding: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 12,
elevation: 8,
},
label: { fontSize: 12, color: '#999', marginBottom: 4 },
value: { fontSize: 16, fontWeight: '600', marginBottom: 16 },
button: {
backgroundColor: '#007AFF',
borderRadius: 10,
paddingVertical: 12,
alignItems: 'center',
},
buttonText: { color: '#fff', fontSize: 15, fontWeight: '600' },
});Scanning with torch toggle
import React, { useState } from 'react';
import { View, Pressable, Text, StyleSheet } from 'react-native';
import { Scanner } from 'react-native-scanner-pro';
export default function TorchScreen() {
const [torch, setTorch] = useState(false);
return (
<View style={{ flex: 1 }}>
<Scanner
style={StyleSheet.absoluteFill}
torch={torch}
onCodeScanned={(result) => console.log(result.data)}
/>
<Pressable
style={[styles.torchBtn, torch && styles.torchBtnActive]}
onPress={() => setTorch(v => !v)}
>
<Text style={styles.torchIcon}>{torch ? '🔦' : '💡'}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
torchBtn: {
position: 'absolute',
top: 60,
right: 20,
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
},
torchBtnActive: {
backgroundColor: 'rgba(255,200,0,0.8)',
},
torchIcon: { fontSize: 22 },
});Scan history list
Keep track of everything scanned in the current session:
import React, { useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { Scanner } from 'react-native-scanner-pro';
export default function HistoryScanner() {
const [history, setHistory] = useState([]);
function handleScan(result) {
setHistory(prev => {
// Avoid duplicates
if (prev.some(item => item.data === result.data)) return prev;
return [{ data: result.data, type: result.type, id: Date.now() }, ...prev];
});
}
return (
<View style={{ flex: 1 }}>
<Scanner
style={styles.camera}
onCodeScanned={handleScan}
scanRegion={{ enabled: true, width: 280, height: 200, offsetY: -60 }}
/>
<View style={styles.historyPanel}>
<Text style={styles.historyTitle}>
{history.length === 0 ? 'No scans yet' : `${history.length} code(s) scanned`}
</Text>
<FlatList
data={history}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<View style={styles.historyItem}>
<Text style={styles.historyType}>{item.type}</Text>
<Text style={styles.historyData} numberOfLines={1}>{item.data}</Text>
</View>
)}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
camera: { flex: 1 },
historyPanel: { height: 240, backgroundColor: '#fff', padding: 16 },
historyTitle: { fontSize: 13, color: '#999', marginBottom: 8 },
historyItem: {
paddingVertical: 10,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#eee',
},
historyType: { fontSize: 11, color: '#999', marginBottom: 2 },
historyData: { fontSize: 14, color: '#333' },
});Professional checkout scanner
This is a more complete example that combines everything — scan region, freeze frame, pro overlay, haptic feedback, and a result card:
import React, { useRef, useState } from 'react';
import {
View, Text, Pressable, StyleSheet, SafeAreaView
} from 'react-native';
import { Scanner } from 'react-native-scanner-pro';
export default function CheckoutScanner() {
const scannerRef = useRef(null);
const [scannedItem, setScannedItem] = useState(null);
function handleScan(result) {
setScannedItem(result);
// Camera is paused by freeze frame
}
function handleDone() {
setScannedItem(null);
scannerRef.current?.resumeScanning();
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#000' }}>
<Scanner
ref={scannerRef}
style={StyleSheet.absoluteFill}
enableFreezeFrame={true}
scanRegion={{
enabled: true,
width: 300,
height: 220,
offsetY: -40,
borderColor: '#FFFFFF',
cornerRadius: 16,
showHint: true,
hintText: 'Scan a product barcode',
}}
onCodeScanned={handleScan}
/>
{scannedItem && (
<View style={styles.result}>
<Text style={styles.resultType}>{scannedItem.type}</Text>
<Text style={styles.resultData}>{scannedItem.data}</Text>
<Pressable style={styles.doneBtn} onPress={handleDone}>
<Text style={styles.doneBtnText}>Done</Text>
</Pressable>
</View>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
result: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
padding: 24,
paddingBottom: 40,
},
resultType: {
fontSize: 12,
color: '#888',
textTransform: 'uppercase',
letterSpacing: 1,
marginBottom: 6,
},
resultData: { fontSize: 22, fontWeight: '700', color: '#111', marginBottom: 20 },
doneBtn: {
backgroundColor: '#111',
borderRadius: 12,
paddingVertical: 14,
alignItems: 'center',
},
doneBtnText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});Preventing duplicate scans
If you're scanning continuously without freeze frame, the same code might fire multiple times. A simple way to prevent this is to track the last scanned value and use a cooldown:
const lastScanned = useRef(null);
const cooldown = useRef(false);
function handleScan(result) {
if (cooldown.current) return;
if (result.data === lastScanned.current) return;
lastScanned.current = result.data;
cooldown.current = true;
console.log('New scan:', result.data);
setTimeout(() => {
cooldown.current = false;
}, 1500); // 1.5 second cooldown
}This works well for checkout-type flows where you want to accept the same code again after a short wait.