DEV Community

Cover image for React Native Series: How to save or share  a React-Native  component as an image
majiyd
majiyd

Posted on

23 4

React Native Series: How to save or share a React-Native component as an image

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;



Enter fullscreen mode Exit fullscreen mode

You should see something like this:
save-or-share-react-native-component-as-image

To achieve our goals, we'll need three packages;

Next let's install these packages



# bash 
yarn add react-native-view-shot @react-native-community/cameraroll react-native-share


Enter fullscreen mode Exit fullscreen mode

Now we'll link the packages,



# bash 
cd ios && pod install && cd ..


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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>



Enter fullscreen mode Exit fullscreen mode

While for Android, navigate to AndroidManifest.xml



project_dir -> android -> app -> src -> main -> AndroidManifest.xml


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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;



Enter fullscreen mode Exit fullscreen mode

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 (
    <>
      ...
    </>
  );
};

...



Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
};

...




Enter fullscreen mode Exit fullscreen mode

And we're done. YAAAAAY!!

In the end, your code should look like this.

import React from 'react';
import {
SafeAreaView,
StyleSheet,
StatusBar,
Text,
View,
TextInput,
TouchableOpacity,
Image,
Platform,
PermissionsAndroid,
Alert,
ActivityIndicator,
} from 'react-native';
import CameraRoll from '@react-native-community/cameraroll';
import RNFetchBlob from 'rn-fetch-blob';
class App extends React.Component {
state = {
url: '',
saving: false,
};
updateUrl = url => {
this.setState({url});
};
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(
'Save remote Image',
'Grant Me Permission to save Image',
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
} catch (err) {
Alert.alert(
'Save remote Image',
'Failed to save Image: ' + err.message,
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
}
};
handleDownload = async () => {
// if device is android you have to ensure you have permission
if (Platform.OS === 'android') {
const granted = await this.getPermissionAndroid();
if (!granted) {
return;
}
}
this.setState({saving: true});
RNFetchBlob.config({
fileCache: true,
appendExt: 'png',
})
.fetch('GET', this.state.url)
.then(res => {
CameraRoll.saveToCameraRoll(res.data, 'photo')
.then(() => {
Alert.alert(
'Save remote Image',
'Image Saved Successfully',
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
})
.catch(err => {
Alert.alert(
'Save remote Image',
'Failed to save Image: ' + err.message,
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
})
.finally(() => this.setState({saving: false}));
})
.catch(error => {
this.setState({saving: false});
Alert.alert(
'Save remote Image',
'Failed to save Image: ' + error.message,
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
});
};
render() {
const {url, saving} = this.state;
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.app}>
{saving ? (
<View style={styles.loader}>
<ActivityIndicator size="large" />
</View>
) : (
<>
<Text style={styles.headerText}>
React Native Image Downloader
</Text>
<View style={styles.textInputWrapper}>
<TextInput
placeholder="Enter image url here"
style={styles.textInput}
value={url}
onChangeText={text => this.updateUrl(text)}
/>
</View>
<Image source={{uri: url}} style={styles.imagePreview} />
<TouchableOpacity
style={styles.downloadButton}
onPress={this.handleDownload}>
<Text>Download Image</Text>
</TouchableOpacity>
</>
)}
</View>
</SafeAreaView>
</>
);
}
}
export default App;
const styles = StyleSheet.create({
container: {
backgroundColor: '#2FF345CC',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
app: {
backgroundColor: '#11131B',
flex: 1,
alignSelf: 'stretch',
alignItems: 'center',
paddingVertical: 30,
},
headerText: {
marginTop: 50,
fontSize: 26,
color: 'white',
},
textInputWrapper: {
marginTop: 30,
alignSelf: 'stretch',
padding: 10,
},
textInput: {
padding: 10,
backgroundColor: '#EFEFEF',
borderWidth: 1,
borderColor: '#DDD',
borderRadius: 3,
},
imagePreview: {
height: 300,
width: 300,
backgroundColor: 'purple',
marginTop: 30,
},
downloadButton: {
backgroundColor: 'white',
marginTop: 40,
paddingHorizontal: 40,
paddingVertical: 20,
borderRadius: 3,
},
loader: {
flex: 1,
justifyContent: 'center',
},
});

Here's a video demo.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (5)

Collapse
 
mpeyper profile image
Michael Peyper

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 to react-native-view-shot instead) and the correct link is github.com/react-native-share/reac....

Collapse
 
guillermodv profile image
guillermodv

I am going to use it today! thanks!

Collapse
 
wjimmycook profile image
Jimmy Cook

Awesome tutorial, worked like a charm. thanks!

Collapse
 
majiyd profile image
majiyd

Glad It did

Collapse
 
stephencavender profile image
Steve

Great tutorial, thank you!

fyi the link to react-native-share goes to react-native-view-shot

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay