Handling Device Hardware: Camera, GPS Location, and Sensors in React Native
📋 Table of Contents
- React Native Hardware Access in 2026
- Camera Integration: Choosing the Right Library
- Expo Camera: Standard Photo & Video Capture
- VisionCamera: Advanced Camera with Frame Processing
- GPS Location Tracking: Precision & Battery Optimization
- Device Sensors: Accelerometer, Gyroscope, Magnetometer
- Permission Handling: iOS & Android Best Practices
- Performance Optimization for Hardware Access
- Testing Hardware Features on Simulators & Real Devices
- Conclusion: Building Hardware-First Mobile Experiences
React Native Hardware Access in 2026
Mobile apps in 2026 are expected to do more than display content — they must interact with the physical world. Whether it's a fitness tracker reading accelerometer data, a delivery app tracking GPS coordinates, or a social platform applying real-time filters to camera frames, hardware access is no longer a premium feature. It's table stakes.
React Native has matured significantly in this area. The New Architecture (Fabric + TurboModules + JSI) has eliminated the JavaScript bridge bottleneck, enabling frame-by-frame camera processing at 60 FPS with zero overhead. Expo's module ecosystem has grown to over 50 native APIs, and specialized libraries like react-native-vision-camera have become the standard for advanced computer vision tasks.
This guide covers the three pillars of React Native hardware access in 2026: camera integration, GPS location tracking, and device sensors. You'll get production-ready code, performance benchmarks, and the decision framework for choosing between Expo's managed workflow and bare React Native CLI.
Camera Integration: Choosing the Right Library
Camera integration in React Native is not one-size-fits-all. The ecosystem has stratified into three distinct layers, each serving different use cases:
- Expo ImagePicker: Uses the native OS image picker for gallery selection or quick photo capture — no custom UI needed
- Expo Camera: Full custom camera preview with photo/video capture, built into the Expo ecosystem, works in Expo Go
- react-native-vision-camera: High-performance camera with frame processors running on the GPU thread — real-time ML inference, QR scanning, and computer vision
| Feature | Expo ImagePicker | Expo Camera | VisionCamera |
|---|---|---|---|
| Native OS Picker | Yes | No | No |
| Custom Camera UI | No | Yes | Yes |
| Frame Processors (Real-time ML) | No | No | Yes (GPU thread) |
| QR/Barcode Scanning | No | Basic | Advanced |
| 4K/HDR Video | OS-dependent | No | Yes |
| Works in Expo Go | Yes | Yes | No (requires prebuild) |
| New Architecture Required | No | No | Yes (for frame processors) |
| Setup Complexity | Very Low | Low | High |
Expo Camera: Standard Photo & Video Capture
Expo Managed Workflow
Expo Camera is the go-to solution for 80% of camera use cases in React Native. It provides a CameraView component that renders a live camera preview with full control over facing direction, flash, zoom, and capture quality. Best of all, it works inside Expo Go — no native code changes or prebuild required.
Installation & Setup
npx expo install expo-camera
Basic Camera Screen Implementation
import { CameraView, CameraType, useCameraPermissions } from 'expo-camera';
import { useState, useRef } from 'react';
import { Button, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
export function CameraScreen() {
const [facing, setFacing] = useState<CameraType>('back');
const [permission, requestPermission] = useCameraPermissions();
const cameraRef = useRef<CameraView>(null);
const [photo, setPhoto] = useState<string | null>(null);
if (!permission) {
return <View />; // Loading permissions
}
if (!permission.granted) {
return (
<View style={styles.container}>
<Text>Camera permission is required.</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
const takePhoto = async () => {
if (!cameraRef.current) return;
const pic = await cameraRef.current.takePictureAsync({
quality: 0.9,
base64: false,
skipProcessing: false,
});
if (pic) setPhoto(pic.uri);
};
const toggleFacing = () => {
setFacing((current) => (current === 'back' ? 'front' : 'back'));
};
return (
<View style={styles.container}>
<CameraView style={styles.camera} facing={facing} ref={cameraRef}>
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={toggleFacing} style={styles.button}>
<Text style={styles.text}>Flip</Text>
</TouchableOpacity>
<TouchableOpacity onPress={takePhoto} style={styles.shutterButton} />
</View>
</CameraView>
</View>
);
}
📸 Expo Camera Best For:
- Profile picture uploads and photo galleries
- Basic QR/barcode scanning within Expo managed workflow
- Photo and video capture with custom UI controls
- Rapid prototyping without native code changes
- Teams that prioritize Expo Go for development speed
Video Recording with Expo Camera
const [isRecording, setIsRecording] = useState(false);
const startRecording = async () => {
if (!cameraRef.current) return;
setIsRecording(true);
const video = await cameraRef.current.recordAsync({
maxDuration: 60, // seconds
maxFileSize: 50 * 1024 * 1024, // 50MB
mute: false,
});
setIsRecording(false);
console.log('Video saved:', video.uri);
};
const stopRecording = () => {
cameraRef.current?.stopRecording();
};
VisionCamera: Advanced Camera with Frame Processing
Requires New Architecture / Bare Workflow
react-native-vision-camera is the high-performance camera library for React Native. Its frame processors run on the GPU thread with zero JavaScript bridge overhead, enabling real-time computer vision at 60 FPS. This is the library powering QR scanning, face detection, pose estimation, and on-device ML inference directly from the live camera feed.
Key Capabilities
- Frame Processors: Run on GPU thread — analyze every frame in real-time without blocking the UI
- Real-Time ML: Integrate TensorFlow Lite, ML Kit, or custom models for live inference
- Format Control: Select exact resolution (up to 4K/8K), frame rate (120fps/240fps slow motion), and codec (H.264/H.265)
- Advanced Capture: RAW capture, HDR, night mode, and custom color spaces
Important: VisionCamera requires the New Architecture (JSI/Fabric) and does NOT work in Expo Go. You must use expo prebuild (bare workflow) or React Native CLI. Budget 2–3 days for initial setup and migration if coming from Expo Camera.
Installation & Setup
# Requires New Architecture — ensure it's enabled in your project npm install react-native-vision-camera # iOS cd ios && pod install # Android — no additional steps needed
Basic Camera with Frame Processor
import { Camera, useCameraDevice, useCameraPermission, useFrameProcessor } from 'react-native-vision-camera';
import { useState } from 'react';
import { runAtTargetFps } from 'react-native-vision-camera';
import { scanCodes, CodeType } from 'vision-camera-code-scanner';
export function QRScannerScreen() {
const device = useCameraDevice('back');
const { hasPermission, requestPermission } = useCameraPermission();
const [scannedCode, setScannedCode] = useState<string | null>(null);
const frameProcessor = useFrameProcessor((frame) => {
'worklet'; // Runs on UI thread, not JS thread
runAtTargetFps(5, () => {
'worklet';
const detectedCodes = scanCodes(frame, [CodeType.QR]);
if (detectedCodes.length > 0) {
// Use runOnJS to update React state from worklet
runOnJS(setScannedCode)(detectedCodes[0].value);
}
});
}, []);
if (!hasPermission) {
return (
<View>
<Text>Camera permission required</Text>
<Button onPress={requestPermission} title="Grant" />
</View>
);
}
if (device == null) return <Text>No camera device</Text>;
return (
<View style={{ flex: 1 }}>
<Camera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
frameProcessor={frameProcessor}
pixelFormat="yuv"
/>
{scannedCode && (
<View style={styles.overlay}>
<Text>Scanned: {scannedCode}</Text>
</View>
)}
</View>
);
}
🎯 VisionCamera Best For:
- Real-time QR/barcode scanning at high speed
- On-device ML inference (face detection, pose estimation, OCR)
- AR overlays and computer vision applications
- Professional video recording with format control (4K, HDR, slow-mo)
- Apps where camera performance is a competitive differentiator
Frame Processor Performance Tips
const frameProcessor = useFrameProcessor((frame) => {
'worklet';
// ✅ GOOD: Limit processing frequency
runAtTargetFps(5, () => {
// Process only 5 frames per second, not 60
const codes = scanCodes(frame, [CodeType.QR]);
});
// ✅ GOOD: Downscale frame for faster processing
const resized = resize(frame, {
scale: { width: 320, height: 240 },
pixelFormat: 'rgb',
});
// ❌ BAD: Heavy JS computation in frame processor
// Never do: fetch(), setState(), or complex JS operations
}, []);
GPS Location Tracking: Precision & Battery Optimization
Location tracking is where React Native apps often fail — not because the APIs are hard, but because developers don't understand the trade-offs between accuracy, battery life, and user privacy. In 2026, both iOS and Android have tightened location permission requirements, making proper implementation more critical than ever.
Expo Location: The Standard Approach
Expo Location provides a unified API for foreground and background location, geofencing, and reverse geocoding. It handles platform differences automatically and is the recommended starting point for most apps.
npx expo install expo-location
Foreground Location with Permission Handling
import * as Location from 'expo-location';
import { useState, useEffect } from 'react';
export function useLocation() {
const [location, setLocation] = useState<Location.LocationObject | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
useEffect(() => {
(async () => {
// Request foreground permission
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
// Get current position with high accuracy
let currentLocation = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High, // ~3m accuracy, higher battery usage
});
setLocation(currentLocation);
})();
}, []);
return { location, errorMsg };
}
// Usage in component
export function LocationDisplay() {
const { location, errorMsg } = useLocation();
if (errorMsg) return <Text>{errorMsg}</Text>;
if (!location) return <Text>Loading location...</Text>;
return (
<View>
<Text>Latitude: {location.coords.latitude}</Text>
<Text>Longitude: {location.coords.longitude}</Text>
<Text>Accuracy: {location.coords.accuracy}m</Text>
<Text>Altitude: {location.coords.altitude}m</Text>
</View>
);
}
Background Location Tracking
Background location requires additional permissions and careful battery management. iOS 17+ and Android 14+ have strict requirements for background location access.
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const LOCATION_TASK_NAME = 'background-location-task';
// Define the background task
TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
if (error) {
console.error('Location task error:', error);
return;
}
if (data) {
const { locations } = data as { locations: Location.LocationObject[] };
const { latitude, longitude } = locations[0].coords;
// Send to your backend or store locally
console.log('Background location:', latitude, longitude);
// ⚠️ iOS limits background execution time — batch and sync efficiently
}
});
export async function startBackgroundTracking() {
// Request background permission (separate from foreground)
const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync();
if (backgroundStatus !== 'granted') {
console.log('Background location permission denied');
return;
}
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
accuracy: Location.Accuracy.Balanced, // Balanced accuracy for battery
timeInterval: 10000, // Update every 10 seconds
distanceInterval: 10, // Or every 10 meters
foregroundService: {
notificationTitle: 'Tracking your route',
notificationBody: 'Location is being used in background',
},
// iOS-specific
pausesUpdatesAutomatically: true, // Pause when not moving
activityType: Location.ActivityType.Fitness, // Optimize for fitness tracking
});
}
- Use Balanced Accuracy:
Accuracy.Balancedprovides ~10m accuracy with 40% less battery drain thanAccuracy.High - Set Distance Thresholds: Only update when the user moves at least 10–50 meters, not on a timer
- Pause When Stationary: Enable
pausesUpdatesAutomaticallyon iOS to stop GPS polling when the user isn't moving - Batch Network Requests: Don't send every location update to your server. Buffer 10–20 updates and send in one request
- Use Significant Location Changes: On iOS,
Location.getLastKnownPositionAsync()returns cached data without waking the GPS chip
Geofencing: Enter/Exit Region Monitoring
import * as Location from 'expo-location';
export async function setupGeofencing() {
await Location.startGeofencingAsync('geofence-task', [
{
identifier: 'office',
latitude: 40.7128,
longitude: -74.0060,
radius: 100, // meters
notifyOnEnter: true,
notifyOnExit: true,
},
{
identifier: 'gym',
latitude: 40.7580,
longitude: -73.9855,
radius: 50,
notifyOnEnter: true,
notifyOnExit: false,
},
]);
}
TaskManager.defineTask('geofence-task', ({ data, error }) => {
if (error) return;
const { eventType, region } = data as {
eventType: Location.GeofencingEventType;
region: Location.LocationRegion;
};
if (eventType === Location.GeofencingEventType.Enter) {
console.log(`Entered ${region.identifier}`);
// Trigger local notification or update UI
} else {
console.log(`Exited ${region.identifier}`);
}
});
Device Sensors: Accelerometer, Gyroscope, Magnetometer
React Native provides access to the full suite of device sensors through Expo's sensor modules. These enable fitness tracking, gaming controls, step counting, compass functionality, and motion-based UI interactions.
Available Sensors in 2026
| Sensor | Module | Use Case | Update Rate |
|---|---|---|---|
| Accelerometer | expo-sensors |
Shake detection, step counting, tilt controls | Up to 100Hz |
| Gyroscope | expo-sensors |
Rotation tracking, gaming, VR/AR | Up to 100Hz |
| Magnetometer | expo-sensors |
Compass, metal detection | Up to 50Hz |
| Barometer | expo-sensors |
Altitude estimation, weather | ~1Hz |
| Pedometer | expo-sensors |
Step counting, fitness tracking | Event-driven |
| Light Sensor | expo-sensors |
Auto-brightness, adaptive UI | ~5Hz |
Accelerometer: Shake Detection
import { Accelerometer } from 'expo-sensors';
import { useState, useEffect } from 'react';
const SHAKE_THRESHOLD = 1.5; // G-force
export function useShakeDetection(onShake: () => void) {
const [subscription, setSubscription] = useState<any>(null);
useEffect(() => {
const subscribe = () => {
setSubscription(
Accelerometer.addListener((accelerometerData) => {
const { x, y, z } = accelerometerData;
const magnitude = Math.sqrt(x * x + y * y + z * z);
if (magnitude > SHAKE_THRESHOLD) {
onShake();
}
})
);
};
// Set update interval (default is 100ms = 10Hz)
Accelerometer.setUpdateInterval(100);
subscribe();
return () => {
subscription && subscription.remove();
setSubscription(null);
};
}, [onShake]);
return subscription;
}
// Usage
export function ShakeToRefresh() {
const [lastShake, setLastShake] = useState<Date>(new Date());
useShakeDetection(() => {
setLastShake(new Date());
// Trigger refresh logic
});
return <Text>Last shake: {lastShake.toLocaleTimeString()}</Text>;
}
Gyroscope: Rotation Tracking
import { Gyroscope } from 'expo-sensors';
import { useState, useEffect } from 'react';
import { Animated } from 'react-native';
export function GyroscopeRotation() {
const [rotation] = useState(new Animated.ValueXY({ x: 0, y: 0 }));
useEffect(() => {
Gyroscope.setUpdateInterval(16); // ~60Hz for smooth animation
const subscription = Gyroscope.addListener((data) => {
// Map gyroscope data to rotation angles
// x = pitch, y = roll, z = yaw
rotation.setValue({
x: data.y * 50, // Scale factor for visual effect
y: data.x * 50,
});
});
return () => subscription.remove();
}, []);
const rotateX = rotation.x.interpolate({
inputRange: [-100, 100],
outputRange: ['-30deg', '30deg'],
});
const rotateY = rotation.y.interpolate({
inputRange: [-100, 100],
outputRange: ['30deg', '-30deg'],
});
return (
<Animated.View
style={[
styles.box,
{
transform: [
{ rotateX },
{ rotateY },
],
},
]}
/>
);
}
Pedometer: Step Counting
import { Pedometer } from 'expo-sensors';
import { useState, useEffect } from 'react';
export function usePedometer() {
const [isAvailable, setIsAvailable] = useState(false);
const [stepCount, setStepCount] = useState(0);
const [pastSteps, setPastSteps] = useState(0);
useEffect(() => {
const subscribe = async () => {
const isStepCountingAvailable = await Pedometer.isAvailableAsync();
setIsAvailable(isStepCountingAvailable);
if (isStepCountingAvailable) {
// Get steps from midnight today
const start = new Date();
start.setHours(0, 0, 0, 0);
const result = await Pedometer.getStepCountAsync(start, new Date());
setPastSteps(result.steps);
// Subscribe to real-time updates
return Pedometer.watchStepCount((result) => {
setStepCount(result.steps);
});
}
};
const subscription = subscribe();
return () => {
subscription?.then(sub => sub?.remove());
};
}, []);
return { isAvailable, todaySteps: pastSteps + stepCount };
}
Permission Handling: iOS & Android Best Practices
Both iOS and Android have tightened permission requirements significantly. iOS 17+ requires runtime permission dialogs with clear usage descriptions, and Android 14+ mandates foreground service types for background location. Getting this wrong means app store rejection or crashes.
iOS Permission Configuration
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <!-- Camera --> <key>NSCameraUsageDescription</key> <string>This app uses the camera to scan QR codes and take photos.</string> <!-- Location --> <key>NSLocationWhenInUseUsageDescription</key> <string>Your location is used to show nearby restaurants and track delivery progress.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>Background location is used to track your running route even when the app is closed.</string> <!-- Motion Sensors --> <key>NSMotionUsageDescription</key> <string>Motion data is used to count your daily steps and detect shake gestures.</string> <!-- Photo Library --> <key>NSPhotoLibraryUsageDescription</key> <string>Photos are used to set your profile picture and share images with friends.</string> </dict> </plist>
Android Permission Configuration
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Camera --> <uses-permission android:name="android.permission.CAMERA" /> <!-- Location --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- Foreground service for background location (Android 14+) --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <!-- Sensors --> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" /> <!-- Hardware features --> <uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.location.gps" android:required="false" /> <uses-feature android:name="android.hardware.sensor.accelerometer" android:required="false" /> </manifest>
Permission Best Practice: Always request permissions in context — when the user taps "Take Photo," not at app launch. Explain why you need the permission before showing the system dialog. iOS apps that request permissions at launch without context face higher rejection rates.
Performance Optimization for Hardware Access
Hardware access is the fastest way to drain battery and degrade app performance. These are the non-negotiable practices for production apps.
Camera Performance
- Release Camera When Backgrounded: Always pause the camera preview when the app enters background. iOS will terminate apps that hold the camera in background.
- Use Appropriate Resolution: Don't capture 4K photos for profile pictures. Set
quality: 0.7and appropriate dimensions. - Compress Before Upload: Use
ImageManipulatorfrom Expo to resize and compress images before network upload. - Frame Processor Throttling: Limit frame processors to 5–10 FPS for analysis tasks. Processing every frame at 60 FPS drains battery in minutes.
Location Performance
- Use Significant Location Changes: For apps that don't need constant tracking, iOS's significant location change API wakes your app only when the user moves ~500 meters.
- Batch Updates: Collect 10–20 location points before syncing to your server. Each network request wakes the radio chip, which is expensive.
- Stop When Not Needed: Automatically stop location tracking when the user reaches their destination or after a timeout.
Sensor Performance
- Reduce Update Frequency: 10Hz (100ms interval) is sufficient for most use cases. 100Hz drains battery 10x faster with minimal UX benefit.
- Unsubscribe on Unmount: Always remove sensor listeners when components unmount. Orphaned listeners keep sensors active and drain battery.
- Use Hardware Step Counter: On Android, prefer
hardwareStepCounteroverhardwareAccelerometerfor step counting — it's more accurate and battery-efficient.
Testing Hardware Features on Simulators & Real Devices
Hardware features cannot be fully tested on simulators. Here's the testing strategy that catches 99% of production issues.
| Feature | Simulator Testing | Real Device Testing | Automated Testing |
|---|---|---|---|
| Camera | Basic preview (macOS camera) | Required — focus, flash, performance | Detox + mock camera module |
| Location | Simulated coordinates | Required — GPS accuracy, background behavior | Mock location provider |
| Sensors | Not available | Required — all sensor data | Mock sensor data injection |
| Permissions | System dialogs | Required — first-time user flow | XCUITest / Espresso |
📱 Ship Hardware-First Apps with Confidence
Need help integrating camera, GPS, or sensors into your React Native app? Our team has shipped 50+ production apps with advanced hardware features. Get a code review, architecture assessment, or full implementation support for your next project.
Book a Free ConsultationConclusion: Building Hardware-First Mobile Experiences
React Native in 2026 is no longer limited to simple UI apps. The New Architecture, Expo's 50+ native modules, and specialized libraries like VisionCamera have transformed it into a platform capable of handling the most demanding hardware use cases — real-time computer vision, precision GPS tracking, and high-frequency sensor data.
The key to success is not choosing the most powerful library, but the right library for your use case. Expo Camera covers 80% of camera needs without leaving the managed workflow. VisionCamera is there when you need frame-by-frame analysis. Expo Location provides everything for GPS tracking, and expo-sensors unlocks the full suite of device sensors with a unified API.
Performance and battery optimization are not afterthoughts — they're requirements. Throttle frame processors, batch location updates, reduce sensor frequency, and always unsubscribe listeners. Users will abandon apps that drain their battery, regardless of how impressive the features are.
The hardware is there. The APIs are mature. The only question is what you'll build with them.
Your Next Step: Pick one hardware feature your app needs but doesn't have yet. Implement it this week using the code examples in this guide. Start with Expo Camera or Expo Location — they're the fastest path from idea to working prototype.