npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cloudstrytech/react-expo-camera

v0.1.0

Published

A camera component for React Native / Expo apps on Android. Live preview, permissions, zoom, capture shapes and automatic image optimisation.

Readme

@cloudstrytech/react-expo-camera

A camera component for React Native / Expo apps on Android.

One package, one job: a camera. There is no second component and no subpath import — install it, import CloudstryCamera, done.

Status: 0.1.0. CloudstryCamera is working: live preview, permissions, capture, front/back switch, close button, pinch and slider zoom, five capture shapes, and automatic image optimisation. Checked on 3 real Android devices.


Install

npm install @cloudstrytech/react-expo-camera
npx expo install expo-camera expo-image-manipulator

Both are peer dependencies — declared, not bundled. If the library bundled its own copy, an app that already uses expo-camera would end up with two copies and two native module registrations. Peer dependencies force a single shared copy.

The library itself has zero runtime dependencies and needs no Babel plugin, no root wrapper, and no Metro config.


Quick start

import { View } from "react-native";
import { CloudstryCamera } from "@cloudstrytech/react-expo-camera";

export default function Screen() {
  return (
    <View style={{ flex: 1 }}>
      <CloudstryCamera onCapture={(photo) => console.log(photo.uri)} />
    </View>
  );
}

That is all. The component asks for camera permission itself.

Give it a parent with flex: 1. The camera fills its parent — with no height, nothing shows.

You can also import just this component:

import { CloudstryCamera } from "@cloudstrytech/react-expo-camera";

What you get back

{
  uri: "file:///data/user/0/.../photo.jpg",
  width: 1499,
  height: 2665
}

The component stops here. It does not upload and does not save to the gallery — one URI lets your app do either.

The file lives in the app cache. Use it soon after capture; Android can clear cache when storage runs low.


Props

All optional.

Camera

| Prop | Type | Default | What it does | | --- | --- | --- | --- | | facing | "back" | "front" | "back" | Which camera to use | | showFlipButton | boolean | true | Show the front/back switch | | style | ViewStyle | — | Style for the camera container |

Photo shape

| Prop | Type | Default | What it does | | --- | --- | --- | --- | | ratio | "16:9" | "1:1" | "4:5" | "2:3" | "9:16" | "9:16" | Shape of the photo | | allowedRatios | array of the above | all five | Which shapes the user can pick | | showRatioPicker | boolean | true | Show the shape buttons | | onRatioChange | (ratio) => void | — | Fires when the user picks a shape |

The preview is framed to the shape and the saved file is centre-cropped to match, so what the user sees is what they get.

File size

| Prop | Type | Default | What it does | | --- | --- | --- | --- | | quality | number 0–1 | 0.85 | JPEG compression | | maxPixels | number | 4000000 | Pixel ceiling for the saved photo |

Defaults keep a photo near 1–1.5 MB. A raw sensor capture is 7–8 MB. Lower maxPixels for smaller files. Photos are never scaled up.

Zoom

| Prop | Type | Default | What it does | | --- | --- | --- | --- | | zoom | number 0–1 | 0 | Starting zoom | | enableZoom | boolean | true | Turn zoom off completely | | showZoomSlider | boolean | true | Show the slider. Pinch still works | | onZoomChange | (zoom) => void | — | Fires when the user zooms |

Callbacks

| Prop | Type | What it does | | --- | --- | --- | | onCapture | (photo) => void | A photo was taken | | onCancel | () => void | User tapped the close button | | onError | (error) => void | Something failed |

The close button appears only if you pass onCancel. The library does not close itself — it cannot know how it was mounted. onCancel is the signal; your app unmounts the component, and that unmount releases the camera. It also adds a Cancel option to the permission screen, so a user who denies permission is never stuck.


Recipes

Fixed shape, no choice

<CloudstryCamera ratio="4:5" showRatioPicker={false} />

Only some shapes — one entry hides the picker, since one button is not a choice.

<CloudstryCamera allowedRatios={["1:1", "9:16"]} />

Plain shutter only

<CloudstryCamera enableZoom={false} showFlipButton={false} />

Many photos — the camera never closes itself, so just keep it mounted.

const [photos, setPhotos] = useState([]);

<CloudstryCamera onCapture={(p) => setPhotos((list) => [...list, p])} />

Save to the gallery — install expo-media-library in your app.

import * as MediaLibrary from "expo-media-library";

const [perm, requestPerm] = MediaLibrary.usePermissions({ writeOnly: true });

<CloudstryCamera
  onCapture={async (photo) => {
    if (!perm?.granted) await requestPerm();
    await MediaLibrary.saveToLibraryAsync(photo.uri);
  }}
/>

Upload

onCapture={async (photo) => {
  const blob = await fetch(photo.uri).then((r) => r.blob());
  await fetch(presignedUrl, { method: "PUT", body: blob });
}}

Permissions

Camera permission is handled for you. You write no permission code.

| What happened | What the user sees | | --- | --- | | Allowed | The camera | | Denied | A message with "Try again" | | Denied forever | A message with a button to open system settings |

Pass onCancel for a Cancel option on this screen too.

Gallery permission is separate — ask for it yourself if you save photos.


Not included

Save to gallery · upload · rotate · draggable crop · video · barcodes · filters · iOS

The component returns a URI and stops. Storage and upload are your app's decision, and one URI serves every option.


Types

import type {
  CloudstryCameraProps,
  CapturedPhoto,
  CameraFacing,
  CaptureRatio,
} from "@cloudstrytech/react-expo-camera";

Requirements

| Item | Minimum | | --- | --- | | Platform | Android only | | React | 18 | | React Native | 0.73 | | expo-camera | 16 | | expo-image-manipulator | 13 |

Built and tested against Expo SDK 54.


Troubleshooting

| Problem | Fix | | --- | --- | | Nothing shows | Parent needs flex: 1 | | Camera stays open after a photo | It never closes itself — set your own state in onCapture | | No close button | Pass onCancel | | File missing later | Upload or save right after capture; cache is not permanent | | ratio ignored | allowedRatios excludes it — the allowed list wins | | Files too big | Lower maxPixels |


License

MIT