Adding Screen Capture Functionality React Native
Screen capture functionality has become an essential feature in modern mobile applications. Whether you need to let users share their achievements, generate payment receipts, or debug UI issues programmatically, React Native provides a robust solution through the react-native-view-shot library. This guide explores how to implement reliable screen capture functionality in your React Native applications, covering everything from basic setup to advanced implementation patterns and performance optimization.
Why Programmatic Screen Capture Matters
Programmatic screen capture goes far beyond what default device screenshot functionality provides. While users have always been able to capture their screens using hardware buttons or system shortcuts, developers often need more precise control over when, what, and how content gets captured. The react-native-view-shot library addresses these needs by providing a programmatic API that integrates seamlessly with your React Native application development workflow.
Limitations of Default Screenshot Methods
Default device screenshot functionality captures the entire screen, often including elements that users don't intend to share, such as navigation bars, status bars, and other UI components that should remain private or irrelevant to the capture's purpose. This lack of precision creates friction in user experiences where specific content needs to be captured and shared.
Traditional screenshot methods also provide no programmatic control over timing, quality, or format. Developers cannot trigger captures in response to specific application states or events, limiting the integration of screenshots into application workflows. Furthermore, there's no way to automatically save captures to specific locations or process them before storage.
These limitations become particularly problematic for applications that need to generate receipts, share achievements, or document user progress. The inability to capture only specific views means users must manually crop or edit screenshots, adding unnecessary friction to otherwise smooth user experiences.
Benefits of Implementing Custom Capture
Programmatic screen capture through react-native-view-shot addresses these limitations by allowing developers to capture specific views within the application. This targeted approach ensures only the relevant content is included in the resulting image, improving both user experience and data privacy.
Key advantages include:
- Precise view targeting: Capture only the content that matters, whether it's a receipt, chart, or achievement card
- Configurable output formats and quality: Choose between PNG, JPG, or HEIC formats with adjustable quality settings
- Programmatic trigger control: Initiate captures in response to user actions, application events, or automated workflows
- Seamless integration: Combine capture with sharing, saving, or upload functionality without user intervention
- Consistent cross-platform behavior: Ensure uniform capture experiences across iOS and Android devices
Getting Started with react-native-view-shot
The react-native-view-shot library provides a simple yet powerful API for capturing screenshots in React Native applications. As one of the most widely-used solutions for programmatic screen capture in React Native, it offers comprehensive support for both React Native CLI projects and Expo managed workflows. The library handles the complexities of native view rendering across platforms, providing developers with a unified JavaScript interface for capturing views as images.
Installation for React Native CLI Projects
1npm install react-native-view-shot2# or3yarn add react-native-view-shotInstallation for Expo Projects
1expo install react-native-view-shotImporting and Basic Setup
1import ViewShot, { captureRef, captureScreen } from 'react-native-view-shot';The react-native-view-shot library offers two primary methods for capturing screenshots. The captureRef function takes a view reference and options object, returning a promise that resolves to the image URI. This approach provides fine-grained control over which specific view gets captured. The ViewShot component wraps content to be captured and provides an onCapture callback or ref-based capture method, offering a more declarative approach for component-based implementations.
Both methods support extensive configuration options including output format (PNG, JPG, HEIC), quality settings, dimensions, and result types. The library handles all native implementation details, ensuring consistent behavior across iOS and Android devices.
Implementing ViewShot Component
The ViewShot component serves as a wrapper around any React Native view, enabling screenshot capture of its contents. This approach is ideal for capturing specific sections of your UI without affecting the rest of the screen. By wrapping your content in ViewShot, you can trigger captures programmatically without needing direct access to view references in parent components.
1import { useRef } from 'react';2import { ViewShot, captureRef } from 'react-native-view-shot';3import { View, Text, StyleSheet } from 'react-native';4 5function ReceiptScreen({ transaction }) {6 const viewShotRef = useRef(null);7 8 const handleCapture = async () => {9 try {10 const uri = await captureRef(viewShotRef, {11 format: 'png',12 quality: 0.8,13 result: 'tmpfile'14 });15 console.log('Screenshot saved to:', uri);16 17 // Share or save the captured image18 await Share.share({ url: uri });19 } catch (error) {20 console.error('Capture failed:', error);21 }22 };23 24 return (25 <ViewShot ref={viewShotRef} style={styles.container}>26 <View style={styles.receiptCard}>27 <Text style={styles.header}>PAYMENT RECEIPT</Text>28 <Text style={styles.detail}>Transaction: {transaction.id}</Text>29 <Text style={styles.detail}>Amount: ${transaction.amount}</Text>30 <Text style={styles.detail}>Date: {transaction.date}</Text>31 <Text style={[styles.detail, styles.status]}>32 Status: {transaction.status}33 </Text>34 </View>35 <Button title="Share Receipt" onPress={handleCapture} />36 </ViewShot>37 );38}39 40const styles = StyleSheet.create({41 container: { flex: 1, padding: 20 },42 receiptCard: { padding: 16, backgroundColor: '#fff', borderRadius: 8 },43 header: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },44 detail: { fontSize: 16, marginBottom: 8 },45 status: { color: '#28a745' }46});Configuration Options
The captureRef function accepts an options object with several configuration parameters that control the output format and quality:
- format: Output image format ('png', 'jpg', or 'heic' for iOS). PNG preserves quality but produces larger files, while JPEG offers better compression at the cost of some quality.
- quality: Compression quality for JPEG images (0.0 to 1.0). Higher values produce better quality but larger files.
- width: Desired output width in pixels. The library will scale the image accordingly.
- height: Desired output height in pixels. Use this with width for precise dimensions.
- result: Storage location ('tmpfile', 'base64', 'data-uri'). Choose based on how you plan to use the captured image.
Selecting the right combination of these options depends on your use case. For high-quality prints or archival, use PNG with full quality. For sharing or storage-constrained scenarios, JPEG at 0.8 quality provides a good balance.
1const uri = await captureRef(viewRef, {2 format: 'jpg', // Output format: 'png', 'jpg', or 'heic'3 quality: 0.8, // JPEG quality from 0.0 to 1.04 width: 800, // Output width in pixels5 height: 600, // Output height in pixels6 result: 'tmpfile' // 'tmpfile', 'base64', or 'data-uri'7});Using captureRef Function Directly
For scenarios where wrapping content isn't practical, captureRef can be called directly on any view reference. This approach provides more flexibility for complex layouts where you don't want to modify the component hierarchy. You can use React refs with any standard React Native view component, enabling capture without changing your existing React Native component structure. This pattern is particularly useful when working with third-party UI components or deeply nested view hierarchies.
1import { useRef } from 'react';2import { captureRef } from 'react-native-view-shot';3import { View, Text, StyleSheet } from 'react-native';4 5function DashboardCard() {6 const cardRef = useRef(null);7 8 const onShareCard = async () => {9 try {10 const uri = await captureRef(cardRef, {11 format: 'png',12 result: 'data-uri'13 });14 15 // Pass uri to share functionality16 await Share.share({17 url: uri,18 message: 'Check out my weekly progress!'19 });20 } catch (error) {21 console.error('Capture failed:', error);22 }23 };24 25 return (26 <View ref={cardRef} style={styles.card}>27 <Text style={styles.title}>Weekly Summary</Text>28 <SummaryChart />29 <Button title="Share" onPress={onShareCard} />30 </View>31 );32}33 34const styles = StyleSheet.create({35 card: {36 padding: 16,37 backgroundColor: '#fff',38 borderRadius: 12,39 elevation: 2,40 shadowColor: '#000',41 shadowOffset: { width: 0, height: 2 },42 shadowOpacity: 0.1,43 shadowRadius: 444 },45 title: { fontSize: 18, fontWeight: '600', marginBottom: 12 }46});Handling Capture Results
The captureRef function returns a promise that resolves to the image location. Understanding the result types helps determine how to handle the captured image:
- 'tmpfile': Returns a temporary file path that can be shared or persisted. This is the default and most common choice, as the file can be easily shared, uploaded, or saved to the device's photo library.
- 'base64': Returns base64-encoded string for direct transmission. Useful for uploading to servers without intermediate file handling, though base64 strings are approximately 33% larger than binary data.
- 'data-uri': Returns data URI for immediate use in components. Can be used directly in Image components, but be aware that very large data URIs can impact memory and performance.
Choose the result type based on your next step. For sharing or saving to photos, 'tmpfile' is typically best. For API uploads, either 'tmpfile' or 'base64' works well depending on your backend capabilities.
1const handleExport = async () => {2 // Option 1: Base64 for direct API upload3 const base64Image = await captureRef(viewRef, {4 result: 'base64',5 format: 'png'6 });7 8 await API.uploadScreenshot({9 image: base64Image,10 timestamp: Date.now()11 });12 13 // Option 2: Temporary file for sharing14 const tempFile = await captureRef(viewRef, {15 result: 'tmpfile',16 format: 'jpg',17 quality: 0.918 });19 20 await Share.share({ url: tempFile });21};Advanced Implementation Patterns
Payment Receipt Capture
One of the most common and valuable use cases for programmatic screenshotting is generating shareable payment receipts. This pattern combines capture functionality with sharing integration to provide users with instant receipt sharing capabilities. E-commerce apps, food delivery services, and subscription platforms all benefit from streamlined receipt capture and sharing.
1import { useRef } from 'react';2import { captureRef } from 'react-native-view-shot';3import { View, Text, StyleSheet } from 'react-native';4 5function PaymentReceipt({ transaction, onClose }) {6 const receiptRef = useRef(null);7 8 const captureAndShareReceipt = async () => {9 try {10 const uri = await captureRef(receiptRef, {11 format: 'jpg',12 quality: 0.9,13 width: 600,14 result: 'tmpfile'15 });16 17 await Share.share({18 url: uri,19 message: `Payment receipt from ${transaction.merchant}`20 });21 } catch (error) {22 console.error('Receipt capture failed:', error);23 }24 };25 26 return (27 <View style={styles.container}>28 <View ref={receiptRef} style={styles.receipt}>29 <View style={styles.header}>30 <Text style={styles.logo}>{transaction.merchant}</Text>31 <Text style={styles.receiptTitle}>RECEIPT</Text>32 </View>33 34 <View style={styles.divider} />35 36 <View style={styles.row}>37 <Text style={styles.label}>Transaction ID</Text>38 <Text style={styles.value}>{transaction.id}</Text>39 </View>40 <View style={styles.row}>41 <Text style={styles.label}>Amount</Text>42 <Text style={[styles.value, styles.amount]}>43 ${transaction.amount.toFixed(2)}44 </Text>45 </View>46 <View style={styles.row}>47 <Text style={styles.label}>Date</Text>48 <Text style={styles.value}>{transaction.date}</Text>49 </View>50 <View style={styles.row}>51 <Text style={styles.label}>Status</Text>52 <Text style={[styles.value, styles.status]}>53 {transaction.status}54 </Text>55 </View>56 57 <View style={styles.divider} />58 59 <Text style={styles.thankYou}>Thank you for your business!</Text>60 </View>61 62 <View style={styles.actions}>63 <Button title="Close" onPress={onClose} variant="secondary" />64 <Button title="Share Receipt" onPress={captureAndShareReceipt} />65 </View>66 </View>67 );68}69 70const styles = StyleSheet.create({71 container: { flex: 1, padding: 16, backgroundColor: '#f5f5f5' },72 receipt: { padding: 20, backgroundColor: '#fff', borderRadius: 8 },73 header: { alignItems: 'center', marginBottom: 16 },74 logo: { fontSize: 20, fontWeight: 'bold' },75 receiptTitle: { fontSize: 16, color: '#666', marginTop: 4 },76 divider: { height: 1, backgroundColor: '#e0e0e0', marginVertical: 16 },77 row: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 },78 label: { color: '#666' },79 value: { fontWeight: '500' },80 amount: { fontSize: 18, fontWeight: 'bold' },81 status: { color: '#28a745' },82 thankYou: { textAlign: 'center', color: '#999', marginTop: 16 },83 actions: { flexDirection: 'row', gap: 12, marginTop: 16 }84});Debugging and Error Reporting
Screen capture proves invaluable for debugging UI issues or capturing application state for error reports. By integrating screenshot capture into your error handling and debugging workflows, you can capture visual context along with technical logs, making it easier to diagnose and resolve issues. This pattern wraps components to enable development-time screenshot capture that can be triggered automatically when errors occur.
1import { useRef } from 'react';2import { captureRef } from 'react-native-view-shot';3import { View } from 'react-native';4 5function withScreenshotDebug(WrappedComponent) {6 return function DebugWrapper(props) {7 const contentRef = useRef(null);8 const [debugMode, setDebugMode] = useState(false);9 10 const captureForDebug = async () => {11 if (!__DEV__) return; // Only in development12 13 try {14 const uri = await captureRef(contentRef, {15 format: 'png',16 result: 'tmpfile'17 });18 19 // Log or save the screenshot for debugging20 Logger.logDebugScreenshot({21 uri,22 componentProps: props,23 timestamp: Date.now()24 });25 26 console.log('Debug screenshot captured:', uri);27 } catch (error) {28 console.error('Debug capture failed:', error);29 }30 };31 32 const handleError = async (error) => {33 // Auto-capture on error34 if (contentRef.current && __DEV__) {35 await captureForDebug();36 }37 38 // Re-throw for other error handlers39 throw error;40 };41 42 return (43 <View ref={contentRef} style={{ flex: 1 }}>44 <WrappedComponent {...props} onError={handleError} />45 {__DEV__ && (46 <DebugPanel onCapture={captureForDebug} />47 )}48 </View>49 );50 };51}Dynamic Content Sharing
For applications displaying charts, statistics, or personalized content, screenshot capture enables sharing of dynamic visualizations. Fitness apps, financial trackers, and productivity tools all benefit from the ability to share progress updates, achievements, and statistics. The key is capturing the content at the right moment when the data represents the user's current state.
1import { useRef } from 'react';2import { captureRef } from 'react-native-view-shot';3import { View, StyleSheet } from 'react-native';4 5function ProgressChart({ userData, onShare }) {6 const chartRef = useRef(null);7 8 const shareProgress = async () => {9 try {10 const uri = await captureRef(chartRef, {11 format: 'jpg',12 quality: 1.0,13 result: 'data-uri'14 });15 16 await Share.share({17 uri: uri,18 title: 'My Progress Update',19 message: `Check out my ${userData.period} progress!`20 });21 } catch (error) {22 console.error('Share capture failed:', error);23 }24 };25 26 return (27 <View style={styles.container}>28 <View ref={chartRef} style={styles.chartContainer}>29 <View style={styles.header}>30 <Text style={styles.title}>{userData.title}</Text>31 <Text style={styles.period}>{userData.period}</Text>32 </View>33 34 <ProgressVisualization data={userData} />35 36 <View style={styles.statsRow}>37 <StatBox label="Goal" value={userData.goal} />38 <StatBox label="Completed" value={userData.completed} />39 <StatBox label="Percentage" value={`${userData.percentage}%`} />40 </View>41 </View>42 43 <ShareButton 44 title="Share Progress" 45 onPress={shareProgress} 46 />47 </View>48 );49}50 51const styles = StyleSheet.create({52 container: { padding: 16 },53 chartContainer: { 54 padding: 20, 55 backgroundColor: '#fff', 56 borderRadius: 16,57 elevation: 458 },59 header: { marginBottom: 16 },60 title: { fontSize: 22, fontWeight: 'bold' },61 period: { fontSize: 14, color: '#666' },62 statsRow: { 63 flexDirection: 'row', 64 justifyContent: 'space-between',65 marginTop: 16 66 }67});Performance Considerations
Screen capture operations can impact application performance, particularly with large views or frequent captures. Understanding the performance characteristics of capture operations and implementing optimization strategies helps maintain smooth user experiences even when capturing complex or large views. Following performance best practices ensures your app remains responsive during capture operations.
Optimizing Capture Operations
Capture Timing: Initiate captures outside of animation frames or intensive operations. Use requestAnimationFrame patterns when capturing animated content to ensure the view is in a stable state. Captures during active animations may result in partially rendered or blurry images.
View Complexity: Simpler views capture faster. When possible, isolate the capture target to minimal necessary content rather than entire screens. If you need to capture a receipt or card, don't wrap the entire page--just wrap the specific view. This reduces both capture time and memory usage.
Memory Management: Handle captured images promptly. Use result: 'tmpfile' for immediate sharing to avoid memory buildup. For applications that capture frequently, implement cleanup logic to remove temporary files after they're no longer needed. The device's photo library can accumulate many captured images if not managed properly.
Quality vs. Performance: Higher quality settings (PNG format, full quality) require more processing time and memory. If captures are slow, consider using JPEG format with slightly reduced quality, which significantly reduces processing time with minimal visible impact on most use cases.
1// Wait for animations to complete before capturing2const captureAnimatedContent = async () => {3 // Wait for any active animations to finish4 await new Promise(resolve => requestAnimationFrame(resolve));5 await new Promise(resolve => requestAnimationFrame(resolve));6 7 const uri = await captureRef(viewRef, {8 format: 'png',9 result: 'tmpfile'10 });11 12 return uri;13};14 15// Capture with immediate cleanup16const captureAndCleanup = async (targetRef) => {17 const uri = await captureRef(targetRef, {18 result: 'tmpfile'19 });20 21 try {22 await shareContent(uri);23 } finally {24 // Clean up temporary file after sharing25 await FileSystem.deleteAsync(uri, { idempotent: true });26 }27};28 29// Optimize by capturing smaller views30const captureReceiptOnly = async () => {31 // Only capture the receipt view, not the entire screen32 const uri = await captureRef(receiptViewRef, {33 format: 'jpg',34 quality: 0.8,35 width: 600, // Limit dimensions for faster processing36 result: 'tmpfile'37 });38 39 return uri;40};Error Handling
Robust error handling ensures graceful failures and provides meaningful feedback to users when capture operations don't succeed. Common error scenarios include view not found, insufficient memory, and permission denied issues. Implementing comprehensive error handling prevents crashes and provides actionable feedback to users.
1const safeCapture = async (viewRef, options = {}) => {2 try {3 if (!viewRef.current) {4 throw new Error('View reference not available');5 }6 7 const uri = await captureRef(viewRef, {8 format: 'png',9 quality: 0.8,10 ...options11 });12 13 return { success: true, uri };14 15 } catch (error) {16 // Handle specific error types17 if (error.message.includes('view not found')) {18 console.warn('View ref not ready for capture - waiting for mount');19 return { success: false, error: 'View not ready', retryable: true };20 }21 22 if (error.message.includes('memory')) {23 console.error('Insufficient memory for capture');24 return { success: false, error: 'Insufficient memory', retryable: false };25 }26 27 if (error.message.includes('canceled') || error.message.includes('cancelled')) {28 console.warn('Capture was canceled by user or system');29 return { success: false, error: 'Capture canceled', retryable: false };30 }31 32 console.error('Capture failed:', error);33 return { success: false, error: error.message, retryable: false };34 }35};36 37// Usage with error handling38const handleCapture = async () => {39 const result = await safeCapture(viewShotRef, { format: 'jpg' });40 41 if (result.success) {42 await Share.share({ url: result.uri });43 } else {44 // Show user-friendly error message45 Alert.alert(46 'Capture Failed',47 'Unable to capture the screen. Please try again.',48 [{ text: 'OK' }]49 );50 }51};Everything you need for production-ready screen capture
Precise View Targeting
Capture specific views rather than entire screens, enabling clean, focused screenshots for receipts, cards, and achievements
Flexible Output Options
Support for PNG, JPG, and HEIC formats with configurable quality, dimensions, and result types (tmpfile, base64, data-uri)
Cross-Platform Consistency
Unified API across iOS and Android with automatic handling of platform-specific requirements and permissions
Performance Optimized
Efficient capture operations with memory management best practices for smooth user experiences even with large views
iOS Configuration
iOS requires appropriate permissions for saving to the photo library. The library handles most permissions automatically through the expo-image-picker or react-native-image-picker integrations, but for explicit permission requests or Info.plist configuration, include NSPhotoLibraryUsageDescription.
When targeting iOS 14 and later, you may also need to configure NSPhotoLibraryAddUsageDescription for saving images directly to the photo library without going through the share sheet.
1<!-- Info.plist -->2<key>NSPhotoLibraryUsageDescription</key>3<string>We need access to save screenshots to your photo library</string>4 5<key>NSPhotoLibraryAddUsageDescription</key>6<string>We need permission to save captured receipts and images</string>Android Configuration
Android 10+ requires the SCREENSHOT flag for capturing secure surfaces. The library handles most scenarios automatically, but ensure appropriate SDK targeting in build.gradle. For Android 13 (API 33) and later, photo picker permissions may be needed for saving to specific locations.
The library automatically handles the scoped storage changes introduced in Android 10, using MediaStore for saving captured images appropriately.
1// android/build.gradle2android {3 compileSdkVersion 344 targetSdkVersion 345 6 compileOptions {7 sourceCompatibility JavaVersion.VERSION_178 targetCompatibility JavaVersion.VERSION_179 }10}11 12// For Expo projects in app.json13{14 "expo": {15 "android": {16 "adaptiveIcon": {17 "backgroundColor": "#FFFFFF"18 }19 }20 }21}Common Questions About React Native Screen Capture
Best Practices Summary
Implementing screen capture functionality effectively requires attention to several key areas that ensure reliable, performant, and user-friendly captures across all devices.
Testing Across Platforms: Always test capture functionality on both iOS and Android devices, as subtle differences in image rendering, font rendering, and native view composition can affect results. What looks perfect on an iOS simulator may render differently on an actual Android device.
User Feedback: Provide clear visual feedback during capture operations, as the process may take several hundred milliseconds depending on view complexity. Loading indicators or brief animations help users understand that an action is in progress.
Temporary File Management: Implement proper cleanup of temporary files to avoid storage bloat on user devices. After sharing or uploading captured images, remove temporary files promptly. Consider implementing a periodic cleanup for any orphaned files.
Error Recovery: Handle errors gracefully with user-friendly messaging rather than technical details. Provide clear paths forward when captures fail, such as retry options or alternative sharing methods.
Permission Handling: Request permissions at the appropriate time--not immediately upon app launch, but when the user first attempts a feature that requires them. Explain why the permission is needed to increase user trust and consent rates.
Privacy Considerations: Be mindful of what content might be captured. Avoid accidentally including sensitive information in preview components, and consider adding watermarks or limiting what can be captured in sensitive areas of your application.
Sources
- Gre/react-native-view-shot GitHub - Core library documentation, API options, and implementation details
- LogRocket: Adding screen capture functionality to a React Native app - Installation guide, code examples, practical applications
- OpenReplay: Capturing Screenshots in React Native - Use cases, benefits, and best practices
- Expo Documentation: react-native-view-shot - Expo SDK integration and API reference