React Native Custom Image Picker With Upload Progress

Istvan Keri
4 min readJul 10, 2019

--

Whether you’re building web or native applications, you’ll eventually be tasked with implementing a file uploader and in pursuit of the perfect solution. File uploads have a lot of moving parts. You have file pickers, progress bars, spinner icons, APIs that send progress back to the user with sockets or hooks, etc. Developers often resort to using pre-built components that are not very customizable. For example, React native gives us a pre-built image picker component. I wanted to modify it to use a dark theme but to my surprise, the background color cannot be changed. I decided to roll my own photo uploader from scratch because I wanted more control over the look and user experience. I wanted the upload to happen on a single screen, the design to be clean, the progress bar to show when the user initiates the upload and the gallery to animate out when the upload starts. Let me show you what I mean:

In this article, I’d like to show you how I did it, so without further ado let’s write some code.

I created a react component in my project called UploadScreen. This is where the 3 major components will live: The header, the progress bar, and the file picker. This is also where we fetch the photo data from the device gallery using the CameraRoll component and store it in component state.

import { CameraRoll, TouchableOpacity, View } from 'react-native';....state = { photos: null, selectedPhoto: null };componentDidMount() {
const { navigation } = this.props;
navigation.setParams({ upload: this.upload });
this.getPhotosAsync({ first: 100 });
}
async getPhotosAsync(params) {
return new Promise((res, rej) =>
CameraRoll.getPhotos(params)
.then(data => {
const assets = data.edges;
const photos = assets.map(asset => asset.node.image);
this.setState({ photos });
res({ photos });
})
.catch(rej)
);
}

Once you have your data loaded into your component we need to create a new component. I called this CapaImagePicker and placed it in my components directory.

Image Picker Component

The image picker component takes the CameraRoll data as a prop and uses a horizontal FlatList to display the photos. Here is the FlatList that is in our render function.

<FlatList
horizontal
showsHorizontalScrollIndicator={false}
renderItem={photo => {
return this.renderPhoto(photo);
}}
keyExtractor={(photo, index) => index.toString()}
data={photos}
/>

You will notice that the renderItem prop is a function that calls a method in our component called renderPhoto. This is where each photo will be rendered. When the user clicks the photo it will call the pickPhoto method which will then display the selected photo.

renderPhoto = photo => {
return (
<TouchableOpacity
onPress={() => {
this.pickPhoto(photo.item);
}}
>
<Image source={{ uri: photo.item.uri }} style={styles.gallerImage} />
</TouchableOpacity>
);
};

Here is the full gist of our component.

Upload Progress Component

The upload progress is a functional component. It receives the following props: uploadFilename, uploadFileSize, uploadProgress. These values come from Axios, and I’m storing them in redux state. Redux and Axios are beyond the scope of this article, I promise to write a follow up which will cover upload APIS and other related topics.

For the progress bar, I’m using a library called react-native-progress.

One thing I would like to point out is that the progress bar will only show the file upload progress. After the upload is complete the server will then process the image (upload it to S3) and that could be additional time, so I decided to use an animated icon to let the user know that the upload is being processed.

Below is the gist for this component:

Now that we have both of our major components built out we need to have them talk to each other. We need our parent view to talk to the child components and the child components need to talk to the parent view. To be more specific I wanted to hide the FlatList gallery once the upload is initiated. In order to do this, we need to call a method in our ImagePicker from the parent view. One way to do this is by using refs. In the constructor of the UploadScreen let’s create a ref to the ImagePicker.

constructor() {
super();
this.imagePicker = React.createRef();
}

This allows us to call the toggleGallery() method inside our child ImagePicker component when the user initiates the upload. Also, we need the ImagePicker to set the state of the selectedPhoto. I’m using onChange prop to do this. See the gist below for our final UploadScreen component.

--

--