Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 9 min read

React Native Camera in 2026: expo-camera vs Vision Camera

Search “react native camera” and the top results point at a package that is no longer maintained. That is the first thing to fix. The second is that most camera features do not need a camera screen at all — expo-image-picker hands you the system UI in six lines. This guide covers the three live options, working code for a real capture screen, permissions that pass App Store review, and why your uploads are eight megabytes each.

First: do you need a camera screen?

If the feature is “let the user attach a photo” — a profile picture, a receipt, a listing image — you almost certainly want expo-image-picker, not a camera. It opens the system camera or gallery, handles its own permissions, and gives users the option to pick an existing photo, which is what most of them wanted anyway.

import * as ImagePicker from 'expo-image-picker';

const result = await ImagePicker.launchCameraAsync({
  mediaTypes: ['images'],
  allowsEditing: true,
  aspect: [1, 1],
  quality: 0.7,          // do this here, not later
});

if (!result.canceled) setPhotoUri(result.assets[0].uri);

Build a real camera screen when the capture is the experience: a scanner with a framing overlay, a document capture with edge guides, a multi-shot flow where returning to a picker each time would be miserable. Everything below assumes you are in that case.

The live options

PackageStatusExpo GoBest forShape
expo-cameraCurrent, maintained✅ YesPhotos, video, barcode scanningCameraView + permission hook
react-native-vision-cameraCurrent, community❌ Dev buildReal-time frame processing, MLDevice hooks + worklets
expo-image-pickerCurrent✅ YesJust need a photo, any sourceSystem UI — no camera screen to build
react-native-camera❌ Unmaintained—Nothing — migrate offMost old tutorials use this

The dividing line between the top two is frames. If you need to read every frame as it arrives and run something on it — detect a face, read text live, track a pose — Vision Camera’s frame processors exist for exactly that and expo-camera has no equivalent. If you need a picture or a video file at the end, expo-camera is less setup and less to maintain.

A capture screen with expo-camera

npx expo install expo-camera expo-image-manipulator

Declare why you want the camera in app.json. This string is shown in the system dialog and is checked during App Store review — a vague one is a rejection:

// app.json
{
  "expo": {
    "plugins": [
      ["expo-camera", {
        "cameraPermission": "Allow $(PRODUCT_NAME) to take photos of your receipts."
      }]
    ]
  }
}
import { CameraView, useCameraPermissions } from 'expo-camera';
import * as ImageManipulator from 'expo-image-manipulator';
import { useRef, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';

export default function CaptureScreen({ onCaptured }) {
  const [permission, requestPermission] = useCameraPermissions();
  const [facing, setFacing] = useState('back');
  const [busy, setBusy] = useState(false);
  const cameraRef = useRef(null);

  // permission is null only while it is still loading
  if (!permission) return <View style={styles.fill} />;

  if (!permission.granted) {
    return (
      <View style={styles.center}>
        <Text style={styles.explain}>
          We need the camera to capture receipts. Photos stay on your device
          until you save them.
        </Text>
        <Pressable style={styles.primary} onPress={requestPermission}>
          <Text style={styles.primaryText}>Allow camera</Text>
        </Pressable>
      </View>
    );
  }

  async function capture() {
    if (busy) return;              // double-tap guard — takePicture is slow
    setBusy(true);
    try {
      const photo = await cameraRef.current.takePictureAsync({
        quality: 0.8,
        skipProcessing: true,      // faster shutter on Android
        // never base64: true
      });
      const resized = await ImageManipulator.manipulateAsync(
        photo.uri,
        [{ resize: { width: 1600 } }],
        { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG },
      );
      onCaptured(resized.uri);
    } finally {
      setBusy(false);
    }
  }

  return (
    <View style={styles.fill}>
      <CameraView ref={cameraRef} style={styles.fill} facing={facing} />
      <View style={styles.controls}>
        <Pressable onPress={() => setFacing((f) => (f === 'back' ? 'front' : 'back'))}>
          <Text style={styles.flip}>Flip</Text>
        </Pressable>
        <Pressable
          style={[styles.shutter, busy && { opacity: 0.5 }]}
          onPress={capture}
        />
      </View>
    </View>
  );
}

Three things in there are the difference between a demo and a shippable screen: an explanation before the system dialog, a busy guard so a double tap does not fire two captures, and a resize step before the URI leaves the function. The last one is covered in more depth in the image upload guide.

Scanning without a second package

Barcode and QR scanning is built into the same component — restrict the types you accept so the scanner is not firing on every stray barcode in frame, and guard against the callback running many times per second:

const handled = useRef(false);

<CameraView
  style={StyleSheet.absoluteFillObject}
  barcodeScannerSettings={{ barcodeTypes: ['qr', 'ean13'] }}
  onBarcodeScanned={({ data }) => {
    if (handled.current) return;   // fires continuously otherwise
    handled.current = true;
    onScan(data);
  }}
/>

Without that ref you get the classic bug: one scan navigates forward twelve times. Full treatment in the QR scanner guide.

When you actually need Vision Camera

Vision Camera exposes the camera as devices and formats, and lets you run a worklet on every frame off the JavaScript thread. That is the capability you are buying:

import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';

const device = useCameraDevice('back');
const { hasPermission, requestPermission } = useCameraPermission();

if (!hasPermission) return <PermissionPrompt onPress={requestPermission} />;
if (device == null) return <NoCameraFound />;   // always handle this

<Camera style={StyleSheet.absoluteFill} device={device} isActive photo />

Two costs come with it. It needs a development build, so no Expo Go and a native rebuild whenever it updates. And useCameraDevice can return null — on a simulator, on unusual hardware, or while the list is loading — so a screen that assumes a device exists crashes for a small but real slice of users.

The permission screen is the feature

Camera permission is one of the few taps in your app that a user can only make wrong once. Deny it and the system dialog never reappears — from then on the only route is Settings, and most people do not go. So the screen before the prompt matters more than the camera code after it: say what you capture, say where it goes, and trigger the dialog from a deliberate tap rather than on mount. If permission is already denied, detect it and deep-link into Settings instead of showing a button that silently does nothing.

Capture screens are also the fastest kind of screen to get wrong on a real device and never notice in a simulator, which is where a live preview helps. Describe it — “a receipt capture screen with a framing overlay, a flash toggle, and a review step before saving” — and ShipNative generates it as real React Native, permission flow included, running on your own phone.

Frequently Asked Questions

Is react-native-camera still maintained?

No. The original react-native-camera (RNCamera) package is no longer maintained and its own README points people elsewhere. The two live options are expo-camera, maintained by the Expo team, and react-native-vision-camera. Any tutorial importing RNCamera is old enough that its other advice is probably stale too.

expo-camera or react-native-vision-camera?

Use expo-camera unless you have a specific reason not to: it works in Expo Go, installs with expo install, and covers photos, video, and barcode scanning. Choose Vision Camera when you need real-time frame processing — pose detection, OCR on the live preview, custom ML on every frame — or fine control over format, frame rate, and exposure.

How do I ask for camera permission in React Native?

With expo-camera, call the useCameraPermissions hook, render an explanatory screen while permission is undetermined, and call requestPermission from a button the user taps. You also need the platform-level strings: an NSCameraUsageDescription on iOS and the CAMERA permission on Android, both of which the expo-camera config plugin adds when you set them in app.json.

Why are my uploaded photos so large?

Because a modern phone camera produces multi-megabyte images and nothing downsizes them for you. Never pass base64: true — it inflates the payload by about a third and holds the whole image in JavaScript memory. Take the file URI, resize it to the largest dimension you will actually display with expo-image-manipulator, then upload that.

Can I scan barcodes and QR codes without a second library?

Yes. expo-camera has scanning built in: set barcodeScannerSettings with the types you accept and handle onBarcodeScanned. Vision Camera has an equivalent code scanner hook. A separate scanning package is rarely necessary in 2026.

Does the camera work in Expo Go?

expo-camera does, which makes it the fastest way to try a camera feature. Vision Camera does not — it is not part of the Expo Go runtime, so it needs a development build created with expo run:ios or expo run:android.

→

React Native Image Upload

What to do with the file once you have captured it.

Read guide →
→

React Native QR Code Scanner

Scanning is a camera screen with one extra prop — the details are here.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.