A React component that renders a camera preview for React Native apps with photo/video capture, barcode scanning, and cross-platform support.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Expo Camera provides comprehensive barcode and QR code scanning capabilities supporting multiple barcode formats with real-time detection during camera preview and static image scanning from URLs.
Configure the camera component to detect barcodes in real-time during camera preview.
/**
* Configure barcode scanning settings for the camera
*/
interface BarcodeSettings {
/**
* Array of barcode types to detect
*/
barcodeTypes: BarcodeType[];
}
/**
* Callback for barcode detection events
* @param result The detected barcode information
*/
type BarcodeScanningCallback = (result: BarcodeScanningResult) => void;Scan barcodes from static images via URL.
/**
* Scan barcodes from an image at the given URL
* @param url URL of the image to scan
* @param barcodeTypes Array of barcode types to detect (defaults to ['qr'])
* @returns Promise resolving to array of detected barcodes
*/
function scanFromURLAsync(
url: string,
barcodeTypes?: BarcodeType[]
): Promise<BarcodeScanningResult[]>;Native iOS barcode scanner with advanced features.
/**
* Launch the modern iOS barcode scanner
* @returns Promise resolving when scanner is launched
*/
launchModernScanner(): Promise<void>;
/**
* Launch the barcode scanner with configuration options (iOS only)
* @param options Scanning configuration options
* @returns Promise resolving when scanner launches
*/
launchScanner(options?: ScanningOptions): Promise<void>;
/**
* Dismiss the active barcode scanner (iOS only)
* @returns Promise resolving when scanner is dismissed
*/
dismissScanner(): Promise<void>;
/**
* Configuration options for iOS barcode scanner
*/
interface ScanningOptions {
/**
* Types of barcodes to scan for
*/
barcodeTypes: BarcodeType[];
/**
* Enable pinch-to-zoom gesture
* @default true
*/
isPinchToZoomEnabled?: boolean;
/**
* Show guidance text over video
* @default true
*/
isGuidanceEnabled?: boolean;
/**
* Highlight recognized items
* @default false
*/
isHighlightingEnabled?: boolean;
}Complete structure of barcode scanning results.
interface BarcodeScanningResult {
/**
* Detected barcode type
*/
type: string;
/**
* Parsed barcode data
*/
data: string;
/**
* Raw barcode data (Android only)
*/
raw?: string;
/**
* Corner points of the barcode bounding box
* Order varies by platform:
* - Android: topLeft, topRight, bottomRight, bottomLeft
* - iOS: bottomLeft, bottomRight, topLeft, topRight
* - Web: topLeft, bottomLeft, topRight, bottomRight
*/
cornerPoints: BarcodePoint[];
/**
* Bounding box of the detected barcode
*/
bounds: BarcodeBounds;
/**
* Platform-specific extra information (Android only)
*/
extra?: AndroidBarcode;
}
interface BarcodePoint {
x: number;
y: number;
}
interface BarcodeBounds {
/**
* Origin point of bounding box
*/
origin: BarcodePoint;
/**
* Size of bounding box
*/
size: BarcodeSize;
}
interface BarcodeSize {
width: number;
height: number;
}All supported barcode formats across platforms.
type BarcodeType =
| 'aztec'
| 'ean13'
| 'ean8'
| 'qr'
| 'pdf417'
| 'upc_e'
| 'datamatrix'
| 'code39'
| 'code93'
| 'itf14'
| 'codabar'
| 'code128'
| 'upc_a';Extended barcode information available on Android platform.
/**
* Enhanced barcode information for Android platform
*/
type AndroidBarcode =
| ContactInfoBarcode
| GeoPointBarcode
| SmsBarcode
| UrlBarcode
| CalendarEventBarcode
| DriverLicenseBarcode
| EmailBarcode
| PhoneBarcode
| WifiBarcode;
interface ContactInfoBarcode {
type: 'contactInfo';
firstName?: string;
middleName?: string;
lastName?: string;
title?: string;
organization?: string;
email?: string;
phone?: string;
url?: string;
address?: string;
}
interface GeoPointBarcode {
type: 'geoPoint';
lat: string;
lng: string;
}
interface SmsBarcode {
type: 'sms';
phoneNumber?: string;
message?: string;
}
interface UrlBarcode {
type: 'url';
url?: string;
}
interface CalendarEventBarcode {
type: 'calendarEvent';
summary?: string;
description?: string;
location?: string;
start?: string;
end?: string;
}
interface DriverLicenseBarcode {
type: 'driverLicense';
firstName?: string;
middleName?: string;
lastName?: string;
licenseNumber?: string;
expiryDate?: string;
issueDate?: string;
addressStreet?: string;
addressCity?: string;
addressState?: string;
}
interface EmailBarcode {
type: 'email';
address?: string;
subject?: string;
body?: string;
}
interface PhoneBarcode {
type: 'phone';
number?: string;
phoneNumberType?: string;
}
interface WifiBarcode {
type: 'wifi';
ssid?: string;
password?: string;
encryptionType?: string;
}import React, { useState } from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { CameraView, BarcodeScanningResult } from 'expo-camera';
export function QRCodeScanner() {
const [scannedData, setScannedData] = useState<string | null>(null);
const handleBarcodeScanned = (result: BarcodeScanningResult) => {
console.log('Barcode scanned:', result);
setScannedData(result.data);
Alert.alert(
'QR Code Detected',
result.data,
[
{ text: 'OK', onPress: () => setScannedData(null) }
]
);
};
return (
<View style={styles.container}>
<CameraView
style={styles.camera}
facing="back"
onBarcodeScanned={handleBarcodeScanned}
barcodeScannerSettings={{
barcodeTypes: ['qr']
}}
/>
{scannedData && (
<View style={styles.resultContainer}>
<Text style={styles.resultText}>
Last scanned: {scannedData}
</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
resultContainer: {
position: 'absolute',
bottom: 100,
left: 20,
right: 20,
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 15,
borderRadius: 10,
},
resultText: {
color: 'white',
textAlign: 'center',
},
});import React, { useState } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { CameraView, BarcodeScanningResult, BarcodeType } from 'expo-camera';
interface ScannedBarcode {
id: string;
type: string;
data: string;
timestamp: Date;
}
export function MultiFormatBarcodeScanner() {
const [scannedBarcodes, setScannedBarcodes] = useState<ScannedBarcode[]>([]);
const supportedTypes: BarcodeType[] = [
'qr',
'ean13',
'ean8',
'code128',
'code39',
'upc_a',
'upc_e',
'pdf417',
'aztec',
'datamatrix'
];
const handleBarcodeScanned = (result: BarcodeScanningResult) => {
// Avoid duplicate scans of the same barcode
const isDuplicate = scannedBarcodes.some(
barcode => barcode.data === result.data && barcode.type === result.type
);
if (!isDuplicate) {
const newBarcode: ScannedBarcode = {
id: `${Date.now()}-${result.type}`,
type: result.type,
data: result.data,
timestamp: new Date(),
};
setScannedBarcodes(prev => [newBarcode, ...prev].slice(0, 10)); // Keep last 10
console.log('New barcode detected:', {
type: result.type,
data: result.data,
bounds: result.bounds,
cornerPoints: result.cornerPoints,
});
}
};
const renderBarcodeItem = ({ item }: { item: ScannedBarcode }) => (
<View style={styles.barcodeItem}>
<Text style={styles.barcodeType}>{item.type.toUpperCase()}</Text>
<Text style={styles.barcodeData}>{item.data}</Text>
<Text style={styles.barcodeTime}>
{item.timestamp.toLocaleTimeString()}
</Text>
</View>
);
return (
<View style={styles.container}>
<CameraView
style={styles.camera}
facing="back"
onBarcodeScanned={handleBarcodeScanned}
barcodeScannerSettings={{
barcodeTypes: supportedTypes
}}
/>
<View style={styles.overlay}>
<Text style={styles.instructions}>
Point camera at barcode or QR code
</Text>
<Text style={styles.supportedFormats}>
Supports: QR, EAN, UPC, Code128, Code39, PDF417, Aztec, DataMatrix
</Text>
</View>
{scannedBarcodes.length > 0 && (
<View style={styles.resultsContainer}>
<Text style={styles.resultsTitle}>Recent Scans:</Text>
<FlatList
data={scannedBarcodes}
renderItem={renderBarcodeItem}
keyExtractor={item => item.id}
style={styles.resultsList}
/>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
overlay: {
position: 'absolute',
top: 100,
left: 20,
right: 20,
alignItems: 'center',
},
instructions: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
backgroundColor: 'rgba(0,0,0,0.7)',
padding: 10,
borderRadius: 10,
},
supportedFormats: {
color: 'white',
fontSize: 12,
textAlign: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
padding: 5,
borderRadius: 5,
marginTop: 10,
},
resultsContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(255,255,255,0.95)',
maxHeight: 200,
},
resultsTitle: {
fontSize: 16,
fontWeight: 'bold',
padding: 10,
textAlign: 'center',
},
resultsList: {
maxHeight: 150,
},
barcodeItem: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
barcodeType: {
fontSize: 12,
fontWeight: 'bold',
color: '#007AFF',
},
barcodeData: {
fontSize: 14,
marginVertical: 2,
},
barcodeTime: {
fontSize: 10,
color: '#666',
},
});import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet, Alert } from 'react-native';
import { scanFromURLAsync, BarcodeScanningResult, BarcodeType } from 'expo-camera';
export function StaticImageScanner() {
const [imageUrl, setImageUrl] = useState('');
const [isScanning, setIsScanning] = useState(false);
const [results, setResults] = useState<BarcodeScanningResult[]>([]);
const scanImageURL = async () => {
if (!imageUrl.trim()) {
Alert.alert('Error', 'Please enter an image URL');
return;
}
setIsScanning(true);
try {
const barcodeTypes: BarcodeType[] = [
'qr', 'ean13', 'ean8', 'code128', 'code39', 'upc_a', 'upc_e'
];
const detectedBarcodes = await scanFromURLAsync(imageUrl, barcodeTypes);
setResults(detectedBarcodes);
if (detectedBarcodes.length === 0) {
Alert.alert('No Barcodes Found', 'No barcodes were detected in the image');
} else {
Alert.alert(
'Barcodes Found',
`Found ${detectedBarcodes.length} barcode(s)`
);
}
} catch (error) {
Alert.alert('Scan Failed', `Error scanning image: ${error.message}`);
} finally {
setIsScanning(false);
}
};
const scanQROnly = async () => {
if (!imageUrl.trim()) return;
setIsScanning(true);
try {
// Scan for QR codes only (iOS limitation note)
const qrCodes = await scanFromURLAsync(imageUrl, ['qr']);
setResults(qrCodes);
if (qrCodes.length > 0) {
Alert.alert('QR Code Found', qrCodes[0].data);
} else {
Alert.alert('No QR Code Found', 'No QR codes detected in the image');
}
} catch (error) {
Alert.alert('Scan Failed', error.message);
} finally {
setIsScanning(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Static Image Barcode Scanner</Text>
<TextInput
style={styles.input}
placeholder="Enter image URL"
value={imageUrl}
onChangeText={setImageUrl}
multiline
/>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, isScanning && styles.disabledButton]}
onPress={scanImageURL}
disabled={isScanning}
>
<Text style={styles.buttonText}>
{isScanning ? 'Scanning...' : 'Scan All Types'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, isScanning && styles.disabledButton]}
onPress={scanQROnly}
disabled={isScanning}
>
<Text style={styles.buttonText}>
{isScanning ? 'Scanning...' : 'Scan QR Only'}
</Text>
</TouchableOpacity>
</View>
{results.length > 0 && (
<View style={styles.resultsContainer}>
<Text style={styles.resultsTitle}>Scan Results:</Text>
{results.map((result, index) => (
<View key={index} style={styles.resultItem}>
<Text style={styles.resultType}>Type: {result.type}</Text>
<Text style={styles.resultData}>Data: {result.data}</Text>
{result.bounds && (
<Text style={styles.resultBounds}>
Bounds: {result.bounds.size.width} x {result.bounds.size.height}
</Text>
)}
</View>
))}
</View>
)}
<Text style={styles.note}>
Note: On iOS, only QR codes are supported for URL scanning
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 20,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 15,
backgroundColor: 'white',
marginBottom: 20,
minHeight: 100,
textAlignVertical: 'top',
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
button: {
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8,
flex: 0.48,
},
disabledButton: {
backgroundColor: '#ccc',
},
buttonText: {
color: 'white',
textAlign: 'center',
fontWeight: 'bold',
},
resultsContainer: {
backgroundColor: 'white',
borderRadius: 8,
padding: 15,
marginBottom: 20,
},
resultsTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
},
resultItem: {
marginBottom: 15,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
resultType: {
fontWeight: 'bold',
color: '#007AFF',
},
resultData: {
marginTop: 5,
},
resultBounds: {
marginTop: 5,
fontSize: 12,
color: '#666',
},
note: {
fontSize: 12,
color: '#666',
textAlign: 'center',
fontStyle: 'italic',
},
});import React, { useEffect } from 'react';
import { View, TouchableOpacity, Text, StyleSheet, Platform } from 'react-native';
import { CameraView, ScanningOptions, ScanningResult } from 'expo-camera';
export function ModernBarcodeScanner() {
const cameraRef = React.useRef<CameraView>(null);
// Listen for modern scanner results
useEffect(() => {
if (Platform.OS !== 'ios') return;
// Set up event listener for modern barcode scanning
const subscription = CameraManager.addListener(
'onModernBarcodeScanned',
(event: ScanningResult) => {
console.log('Modern scanner result:', event);
// Handle the scanned result
}
);
return () => subscription?.remove();
}, []);
const launchModernScanner = async () => {
if (Platform.OS !== 'ios') {
console.warn('Modern scanner is only available on iOS');
return;
}
if (!cameraRef.current) return;
try {
await cameraRef.current.launchModernScanner();
} catch (error) {
console.error('Failed to launch modern scanner:', error);
}
};
const launchScannerWithOptions = async () => {
if (Platform.OS !== 'ios') return;
try {
const options: ScanningOptions = {
barcodeTypes: ['qr', 'ean13', 'code128'],
isPinchToZoomEnabled: true,
isGuidanceEnabled: true,
isHighlightingEnabled: true,
};
// Note: This would typically be called on a CameraManager instance
await CameraManager.launchScanner(options);
} catch (error) {
console.error('Failed to launch scanner with options:', error);
}
};
const dismissScanner = async () => {
if (Platform.OS !== 'ios') return;
try {
await CameraManager.dismissScanner();
} catch (error) {
console.error('Failed to dismiss scanner:', error);
}
};
if (Platform.OS !== 'ios') {
return (
<View style={styles.container}>
<Text style={styles.unavailable}>
Modern scanner is only available on iOS
</Text>
</View>
);
}
return (
<View style={styles.container}>
<CameraView
ref={cameraRef}
style={styles.camera}
facing="back"
/>
<View style={styles.controls}>
<TouchableOpacity style={styles.button} onPress={launchModernScanner}>
<Text style={styles.buttonText}>Launch Modern Scanner</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={launchScannerWithOptions}>
<Text style={styles.buttonText}>Launch with Options</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={dismissScanner}>
<Text style={styles.buttonText}>Dismiss Scanner</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
unavailable: {
textAlign: 'center',
fontSize: 16,
color: '#666',
marginTop: 100,
},
controls: {
position: 'absolute',
bottom: 50,
left: 20,
right: 20,
},
button: {
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8,
marginBottom: 10,
},
buttonText: {
color: 'white',
textAlign: 'center',
fontWeight: 'bold',
},
});