React Native ViewShot
react-native-view-shot is a utility library for React Native applications, enabling developers to capture any rendered view or the entire screen as an image. It provides functionalities to save the captured image to a file, retrieve it as a Base64 encoded string, or other raw formats. The library is actively maintained, with version 4.0.3 being the current stable release, demonstrating a consistent cadence of updates to support the latest versions of React Native, iOS, and Android platforms. Key differentiators include its robust handling of various view types, including SurfaceView on Android, and continuous improvements in performance and platform compatibility, making it a reliable solution for screenshot and view snapshot requirements within the React Native ecosystem. It also offers dedicated web support since v3.7.0.
Common errors
-
TypeError: Cannot read property 'capture' of null
cause The `useRef` hook's current property is null when `capture()` is called, indicating that the `ViewShot` component has not yet mounted or the ref is not correctly attached.fixEnsure `viewShotRef` is initialized with `useRef(null)` and passed as `ref={viewShotRef}` to the `ViewShot` component. Wrap the `capture()` call in a check `if (viewShotRef.current)` to ensure the component is mounted before attempting to capture. -
Invariant Violation: `ViewShot` has been imported by a default import (as `ViewShot`) but is a named export (as `ViewShot`).
cause `ViewShot` is exported as a default module, but the import statement is attempting to destructure it as a named export.fixChange the import statement from `import { ViewShot } from 'react-native-view-shot'` to `import ViewShot from 'react-native-view-shot'`. -
Error: No permission to write to storage. Please check your app's permissions.
cause The React Native application lacks the necessary platform-specific permissions to save the captured image to the device's storage or photo library.fixFor Android, add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` to `AndroidManifest.xml`. For iOS, add `NSPhotoLibraryUsageDescription` to `Info.plist`. Additionally, request these permissions at runtime if targeting modern Android versions (API 23+) or iOS. -
Error: RNViewShotModule.java:xx: error: cannot find symbol method getArguments() in ReadableNativeMap
cause This Android build error often occurs due to a mismatch between the `react-native-view-shot` version and your `react-native` version, indicating an incompatibility with native module API changes.fixEnsure `react-native-view-shot` is updated to a version compatible with your current React Native installation. Try upgrading `react-native-view-shot` to its latest stable release, then perform a clean Android build by running `cd android && ./gradlew clean && cd ..` before rebuilding your app.
Warnings
- breaking The `width` and `height` options for capture on Android were fixed in v3.4.0 to represent absolute pixel dimensions. If you relied on a previous, potentially buggy behavior where these dimensions were interpreted differently or scaled incorrectly, your captures might change in size or aspect ratio after upgrading.
- gotcha Saving captured images to the device's camera roll or external storage requires appropriate runtime permissions. For Android, `WRITE_EXTERNAL_STORAGE` is needed; for iOS, `NSPhotoLibraryUsageDescription` must be added to `Info.plist`. Without these, attempts to save will fail with permission errors.
- gotcha The `<ViewShot>` component experienced a temporary regression around versions v3.0.0 and v3.0.1, causing it to be broken due to issues with `captureMode` checks. This was addressed and fixed in v3.0.2.
- gotcha `captureScreen` functionality on iOS had reported issues and received fixes in v3.3.0. If you are targeting older iOS versions with a version of `react-native-view-shot` prior to v3.3.0, `captureScreen` might not work reliably.
Install
-
npm install react-native-view-shot -
yarn add react-native-view-shot -
pnpm add react-native-view-shot
Imports
- captureRef
const captureRef = require('react-native-view-shot').captureRefimport { captureRef } from 'react-native-view-shot' - ViewShot
import { ViewShot } from 'react-native-view-shot'import ViewShot from 'react-native-view-shot'
- captureScreen
import ViewShot, { captureScreen } from 'react-native-view-shot'import { captureScreen } from 'react-native-view-shot' - CaptureOptions
import type { CaptureOptions } from 'react-native-view-shot'
Quickstart
import React, { useRef, useState } from 'react';
import { Button, View, Text, Alert, Image, StyleSheet } from 'react-native';
import ViewShot from 'react-native-view-shot';
import { CameraRoll } from '@react-native-camera-roll/camera-roll'; // Peer dependency for saving
// Note: @react-native-camera-roll/camera-roll requires separate installation and linking,
// as well as appropriate permissions (e.g., WRITE_EXTERNAL_STORAGE on Android, NSPhotoLibraryUsageDescription on iOS).
const MyViewCapture = () => {
const viewShotRef = useRef<ViewShot>(null);
const [capturedUri, setCapturedUri] = useState<string | null>(null);
const captureView = async () => {
if (viewShotRef.current) {
try {
// Capture the view with specified options
const uri = await viewShotRef.current.capture();
setCapturedUri(uri);
console.log('Image captured to:', uri);
Alert.alert('Capture Success', `Image URI: ${uri}`);
// Optional: Save to camera roll (requires @react-native-camera-roll/camera-roll and permissions)
// try {
// const result = await CameraRoll.save(uri, { type: 'photo' });
// Alert.alert('Saved', `Image saved to camera roll: ${result}`);
// } catch (saveError) {
// console.error('Failed to save to camera roll:', saveError);
// Alert.alert('Save Error', `Failed to save image: ${saveError}`);
// }
} catch (error) {
console.error('Failed to capture view:', error);
Alert.alert('Capture Error', `Failed to capture view: ${error}`);
}
} else {
Alert.alert('Error', 'ViewShot ref is not available.');
}
};
return (
<View style={styles.container}>
<ViewShot ref={viewShotRef} options={{ format: 'png', quality: 0.9, result: 'data-uri' }} style={styles.captureTarget}>
<Text style={styles.title}>Hello ViewShot!</Text>
<Text style={styles.subtitle}>This is a component that will be captured as an image.</Text>
<Image
source={{ uri: 'https://via.placeholder.com/100/ADD8E6/000000?text=RN' }}
style={styles.image}
/>
</ViewShot>
{capturedUri && (
<View style={styles.previewContainer}>
<Text style={styles.previewText}>Captured Image Preview:</Text>
<Image source={{ uri: capturedUri }} style={styles.previewImage} resizeMode="contain" />
</View>
)}
<Button title="Capture View" onPress={captureView} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
padding: 20,
},
captureTarget: {
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
alignItems: 'center',
marginBottom: 30,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
subtitle: {
fontSize: 16,
textAlign: 'center',
marginBottom: 15,
},
image: {
width: 100,
height: 100,
borderRadius: 5,
},
previewContainer: {
marginTop: 20,
alignItems: 'center',
marginBottom: 20,
},
previewText: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
previewImage: {
width: 250,
height: 250,
borderWidth: 1,
borderColor: '#ccc',
backgroundColor: '#e8e8e8',
},
});
export default MyViewCapture;