In this tutorial, I'm going to teach you how to Save/Share a react native component as an image.
TL/DR Find the full code in this github repo
I'm assuming you already know how to set up a react native project if you don't, please check out the official docs, It's pretty detailed.
Next, let's get rid of the default content of App.js and replace it with this:
// App.js
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
Image,
TouchableOpacity,
} from 'react-native';
const App = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.body}>
<View style={styles.savedComponent}>
<Text style={styles.text}> Component to be saved </Text>
<Image
source={{
uri:
'https://images.pexels.com/photos/380768/pexels-photo-380768.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=75&w=126',
}}
style={styles.image}
/>
<Text style={styles.text}>Some random text, also saved</Text>
</View>
<View style={styles.row}>
<TouchableOpacity style={styles.button}>
<Text>Share</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text>Save</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: 'white',
},
body: {
marginTop: 100,
alignItems: 'center',
},
savedComponent: {
backgroundColor: 'white',
marginBottom: 30,
},
text: {
textAlign: 'center',
},
image: {
width: 252,
height: 150,
alignSelf: 'center',
marginTop: 30,
marginBottom: 5,
},
row: {
alignSelf: 'center',
flexDirection: 'row',
justifyContent: 'space-around',
width: '75%',
},
button: {
backgroundColor: '#ad4fcc',
padding: 15,
paddingHorizontal: 35,
borderRadius: 5,
},
});
export default App;
You should see something like this:
To achieve our goals, we'll need three packages;
- React-native-view-shot - For capturing react-native components
- React-native-cameraroll - For saving images.
- React-native-share - For sharing images (texts, urls, etc)
Next let's install these packages
# bash
yarn add react-native-view-shot @react-native-community/cameraroll react-native-share
Now we'll link the packages,
# bash
cd ios && pod install && cd ..
Normally auto-linking should work, If auto-linking doesn't work please go to the individual package website and follow the instructions for manual linking.
Next, we have to set permission. To be able to save images we need to gain permission from the user to save images to their device.
For iOS, navigate to info.plist
project_dir -> ios -> project_name -> info.plist
add the following block to info.plist
<!-- info.plist -->
<?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>
...
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app would like to save images to your device.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app would like to save images to your device.</string>
...
</dict>
</plist>
While for Android, navigate to AndroidManifest.xml
project_dir -> android -> app -> src -> main -> AndroidManifest.xml
Now update AndroidManifest.xml
like so
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.savereactnativecomponent">
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Finally, let's implement logic!!!! YAAAAASSSSS!!!!
We create a ref
for the component we want to save. Also, we'll create a function to handle the image download and bind it to the save button.
import React, {useRef} from 'react';
...
const App = () => {
// create a ref
const viewRef = useRef();
// get permission on android
const getPermissionAndroid = async () => {
try {
} catch (err) {
// handle error as you please
console.log('err', err);
}
};
// download image
const downloadImage = async () => {
try {
} catch (error) {
console.log('error', error);
}
};
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.body}>
<View style={styles.savedComponent} ref={viewRef}>
...
</View>
<View style={styles.row}>
...
<TouchableOpacity style={styles.button} onPress={downloadImage}>
<Text>Save</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
...
export default App;
Next we import captureRef
from react-native-view-shot
, captureRef
captures the component and cameraroll
saves the captured image to the device.
...
import {
...
PermissionsAndroid,
Alert,
Platform,
} from 'react-native';
import {captureRef} from 'react-native-view-shot';
import CameraRoll from '@react-native-community/cameraroll';
const App = () => {
// create a ref
const viewRef = useRef();
// get permission on android
const getPermissionAndroid = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Image Download Permission',
message: 'Your permission is required to save images to your device',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
return true;
}
Alert.alert(
'',
'Your permission is required to save images to your device',
[{text: 'OK', onPress: () => {}}],
{cancelable: false},
);
} catch (err) {
// handle error as you please
console.log('err', err);
}
};
// download image
const downloadImage = async () => {
try {
// react-native-view-shot caputures component
const uri = await captureRef(viewRef, {
format: 'png',
quality: 0.8,
});
if (Platform.OS === 'android') {
const granted = await getPermissionAndroid();
if (!granted) {
return;
}
}
// cameraroll saves image
const image = CameraRoll.save(uri, 'photo');
if (image) {
Alert.alert(
'',
'Image saved successfully.',
[{text: 'OK', onPress: () => {}}],
{cancelable: false},
);
}
} catch (error) {
console.log('error', error);
}
};
return (
<>
...
</>
);
};
...
And we're done. Try it out and you'll see that the image will be saved as is demonstrated in the video at the end of this article.
Next, we'll implement sharing, Let's create a function shareImage
and bind it to the share
button.
...
import Share from 'react-native-share';
const App = () => {
...
const shareImage = async () => {
try {
// capture component
const uri = await captureRef(viewRef, {
format: 'png',
quality: 0.8,
});
// share
const shareResponse = await Share.open({url: uri});
} catch (error) {
console.log('error', error);
}
};
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.body}>
...
<View style={styles.row}>
<TouchableOpacity style={styles.button} onPress={shareImage}>
<Text>Share</Text>
</TouchableOpacity>
...
</View>
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
...
And we're done. YAAAAAY!!
In the end, your code should look like this.
Here's a video demo.
Top comments (5)
Exactly what I needed, thank you so much!
Just a head's up that the link in the article to
react-native-share
is incorrect (it goes toreact-native-view-shot
instead) and the correct link is github.com/react-native-share/reac....I am going to use it today! thanks!
Awesome tutorial, worked like a charm. thanks!
Glad It did
Great tutorial, thank you!
fyi the link to react-native-share goes to react-native-view-shot